From 3da7ea9c1bcee83ec7eb84b58eae718f121bbdaa Mon Sep 17 00:00:00 2001 From: Zhanhui Li Date: Wed, 24 Aug 2022 20:53:30 +0800 Subject: [PATCH] [Infra Improvement] Build And Run Unit Tests in Parallel (#4878) * Support build with Bazel (#4865) * Support build with Bazel, fixing tests to make them capable of running in concurrency and hermetic. * Make test cases capable of running in parallel. (#4874) --- .bazelrc | 73 ++++++++++ .bazelversion | 1 + .github/workflows/bazel.yml | 22 +++ .github/workflows/{build.yaml => maven.yaml} | 4 +- .gitignore | 5 + .licenserc.yaml | 1 + BUILD.bazel | 46 +++++++ WORKSPACE | 128 +++++++++++++++++ acl/BUILD.bazel | 72 ++++++++++ .../apache/rocketmq/acl/common/AclUtils.java | 90 ++++-------- .../plain/RemoteAddressStrategyFactory.java | 6 +- .../rocketmq/acl/common/AclUtilsTest.java | 129 +++++++----------- bazel/BUILD.bazel | 16 +++ bazel/GenTestRules.bzl | 109 +++++++++++++++ broker/BUILD.bazel | 82 +++++++++++ .../rocketmq/broker/BrokerController.java | 15 +- .../AbstractSendMessageProcessor.java | 11 +- .../rocketmq/broker/BrokerControllerTest.java | 37 +++-- .../filter/MessageStoreWithFilterTest.java | 2 +- .../offset/ConsumerOffsetManagerTest.java | 4 +- .../schedule/ScheduleMessageServiceTest.java | 4 +- client/BUILD.bazel | 65 +++++++++ .../impl/producer/DefaultMQProducerImpl.java | 2 +- .../consumer/DefaultLitePullConsumerTest.java | 6 + .../impl/factory/MQClientInstanceTest.java | 2 +- ...efaultMQLitePullConsumerWithTraceTest.java | 6 + common/BUILD.bazel | 60 ++++++++ .../org/apache/rocketmq/common/MixAll.java | 2 +- .../apache/rocketmq/common/MixAllTest.java | 2 +- .../message/MessageClientIDSetterTest.java | 4 +- .../common/message/MessageDecoderTest.java | 1 + .../common/utils/IOTinyUtilsTest.java | 8 +- container/BUILD.bazel | 82 +++++++++++ controller/BUILD.bazel | 76 +++++++++++ .../impl/DLedgerControllerTest.java | 5 +- filter/BUILD.bazel | 59 ++++++++ logging/BUILD.bazel | 24 ++++ namesrv/BUILD.bazel | 76 +++++++++++ .../rocketmq/namesrv/NamesrvController.java | 5 + .../rocketmq/namesrv/NamesrvStartup.java | 4 +- .../namesrv/NameServerInstanceTest.java | 9 ++ proxy/BUILD.bazel | 97 +++++++++++++ .../rocketmq/proxy/config/Configuration.java | 11 ++ .../v2/common/GrpcClientSettingsManager.java | 3 +- .../proxy/config/InitConfigAndLoggerTest.java | 22 ++- .../grpc/v2/GrpcMessagingApplicationTest.java | 15 +- remoting/BUILD.bazel | 50 +++++++ .../remoting/netty/NettyRemotingClient.java | 3 +- .../remoting/netty/NettyRemotingServer.java | 25 ++-- .../remoting/netty/NettyServerConfig.java | 2 +- .../rocketmq/remoting/RemotingServerTest.java | 9 +- .../remoting/SubRemotingServerTest.java | 8 +- .../org/apache/rocketmq/remoting/TlsTest.java | 46 +++++-- srvutil/BUILD.bazel | 59 ++++++++ .../srvutil/FileWatchServiceTest.java | 0 store/BUILD.bazel | 80 +++++++++++ store/libs/dledger-0.2.8-SNAPSHOT.jar | Bin 0 -> 186014 bytes .../store/config/MessageStoreConfig.java | 4 + .../rocketmq/store/ha/DefaultHAService.java | 28 ++-- .../ha/autoswitch/AutoSwitchHAService.java | 7 +- .../rocketmq/store/AppendCallbackTest.java | 6 +- .../rocketmq/store/BatchPutMessageTest.java | 8 +- .../rocketmq/store/ConsumeQueueTest.java | 4 +- .../DefaultMessageStoreCleanFilesTest.java | 2 +- .../DefaultMessageStoreShutDownTest.java | 5 +- .../store/DefaultMessageStoreTest.java | 66 +++++---- .../org/apache/rocketmq/store/HATest.java | 22 ++- .../rocketmq/store/MultiDispatchTest.java | 6 +- .../apache/rocketmq/store/StoreTestBase.java | 2 +- .../rocketmq/store/timer/StoreTestUtils.java | 2 +- .../store/timer/TimerMessageStoreTest.java | 2 - test/BUILD.bazel | 107 +++++++++++++++ .../rocketmq/test/schema/SchemaTools.java | 2 +- .../apache/rocketmq/test/util/RandomUtil.java | 2 +- .../test/base/IntegrationTestBase.java | 15 +- .../dledger/DLedgerProduceAndConsumeIT.java | 6 +- ...aseBroadCastIT.java => BaseBroadcast.java} | 12 +- ...va => BroadcastNormalMsgNotReceiveIT.java} | 4 +- ...ava => BroadcastNormalMsgRecvCrashIT.java} | 4 +- ...java => BroadcastNormalMsgRecvFailIT.java} | 4 +- ...> BroadcastNormalMsgRecvStartLaterIT.java} | 4 +- ...BroadcastNormalMsgTwoDiffGroupRecvIT.java} | 4 +- .../NormalMsgTwoSameGroupConsumerIT.java | 4 +- ...adCastIT.java => OrderMsgBroadcastIT.java} | 6 +- ...java => BroadcastTwoConsumerFilterIT.java} | 6 +- ... => BroadcastTwoConsumerSubDiffTagIT.java} | 6 +- ...java => BroadcastTwoConsumerSubTagIT.java} | 6 +- .../rocketmq/test/grpc/v2/GrpcBaseIT.java | 6 +- tools/BUILD.bazel | 68 +++++++++ .../tools/command/MQAdminStartup.java | 3 +- ...ClusterAclConfigVersionListSubCommand.java | 2 +- .../ProducerConnectionSubCommand.java | 4 + .../consumer/ConsumerProgressSubCommand.java | 4 + .../consumer/ConsumerStatusSubCommand.java | 4 + .../consumer/GetConsumerConfigSubCommand.java | 5 + .../message/QueryMsgTraceByIdSubCommand.java | 3 + .../namesrv/UpdateKvConfigCommand.java | 4 + .../offset/GetConsumerStatusCommand.java | 5 + .../offset/ResetOffsetByTimeCommand.java | 4 + .../tools/admin/DefaultMQAdminExtTest.java | 2 +- .../broker/CleanUnusedTopicCommandTest.java | 6 +- .../ProducerConnectionSubCommandTest.java | 10 +- .../ConsumerProgressSubCommandTest.java | 11 +- .../ConsumerStatusSubCommandTest.java | 11 +- .../GetConsumerConfigSubCommandTest.java | 18 ++- .../QueryMsgTraceByIdSubCommandTest.java | 15 +- .../namesrv/UpdateKvConfigCommandTest.java | 11 +- .../offset/GetConsumerStatusCommandTest.java | 17 ++- .../offset/ResetOffsetByTimeCommandTest.java | 11 +- .../command/server/ServerResponseMocker.java | 10 +- 110 files changed, 1968 insertions(+), 407 deletions(-) create mode 100644 .bazelrc create mode 100644 .bazelversion create mode 100644 .github/workflows/bazel.yml rename .github/workflows/{build.yaml => maven.yaml} (89%) create mode 100644 BUILD.bazel create mode 100644 WORKSPACE create mode 100644 acl/BUILD.bazel create mode 100644 bazel/BUILD.bazel create mode 100644 bazel/GenTestRules.bzl create mode 100644 broker/BUILD.bazel create mode 100644 client/BUILD.bazel create mode 100644 common/BUILD.bazel create mode 100644 container/BUILD.bazel create mode 100644 controller/BUILD.bazel create mode 100644 filter/BUILD.bazel create mode 100644 logging/BUILD.bazel create mode 100644 namesrv/BUILD.bazel create mode 100644 proxy/BUILD.bazel create mode 100644 remoting/BUILD.bazel create mode 100644 srvutil/BUILD.bazel rename srvutil/src/{main/test => test/java}/org/apache/rocketmq/srvutil/FileWatchServiceTest.java (100%) create mode 100644 store/BUILD.bazel create mode 100644 store/libs/dledger-0.2.8-SNAPSHOT.jar create mode 100644 test/BUILD.bazel rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/{BaseBroadCastIT.java => BaseBroadcast.java} (87%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/{BroadCastNormalMsgNotRecvIT.java => BroadcastNormalMsgNotReceiveIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/{BroadCastNormalMsgRecvCrashIT.java => BroadcastNormalMsgRecvCrashIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/{BroadCastNormalMsgRecvFailIT.java => BroadcastNormalMsgRecvFailIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/{BroadCastNormalMsgRecvStartLaterIT.java => BroadcastNormalMsgRecvStartLaterIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/{BroadCastNormalMsgTwoDiffGroupRecvIT.java => BroadcastNormalMsgTwoDiffGroupRecvIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/{OrderMsgBroadCastIT.java => OrderMsgBroadcastIT.java} (97%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/{BroadCastTwoConsumerFilterIT.java => BroadcastTwoConsumerFilterIT.java} (95%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/{BroadCastTwoConsumerSubDiffTagIT.java => BroadcastTwoConsumerSubDiffTagIT.java} (95%) rename test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/{BroadCastTwoConsumerSubTagIT.java => BroadcastTwoConsumerSubTagIT.java} (95%) create mode 100644 tools/BUILD.bazel diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000000..8409b108f27 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +startup --host_jvm_args=-Xmx2g + +run --color=yes + +build --color=yes +build --enable_platform_specific_config + +test --action_env=TEST_TMPDIR=/tmp + +test --experimental_strict_java_deps=warn +build --experimental_strict_java_deps=warn + + +# This .bazelrc file contains all of the flags required for the provided +# toolchain with Remote Build Execution. +# Note your WORKSPACE must contain an rbe_autoconfig target with +# name="rbe_default" to use these flags as-is. + +# Depending on how many machines are in the remote execution instance, setting +# this higher can make builds faster by allowing more jobs to run in parallel. +# Setting it too high can result in jobs that timeout, however, while waiting +# for a remote machine to execute them. +build:remote --jobs=150 + +# Set several flags related to specifying the platform, toolchain and java +# properties. +# These flags should only be used as is for the rbe-ubuntu16-04 container +# and need to be adapted to work with other toolchain containers. +build:remote --java_runtime_version=rbe_jdk +build:remote --tool_java_runtime_version=rbe_jdk +build:remote --extra_toolchains=@rbe_default//java:all + +build:remote --crosstool_top=@rbe_default//cc:toolchain +build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +# Platform flags: +# The toolchain container used for execution is defined in the target indicated +# by "extra_execution_platforms", "host_platform" and "platforms". +# More about platforms: https://docs.bazel.build/versions/master/platforms.html +build:remote --extra_toolchains=@rbe_default//config:cc-toolchain +build:remote --extra_execution_platforms=@rbe_default//config:platform +build:remote --host_platform=@rbe_default//config:platform +build:remote --platforms=@rbe_default//config:platform + +# Starting with Bazel 0.27.0 strategies do not need to be explicitly +# defined. See https://github.com/bazelbuild/bazel/issues/7480 +build:remote --define=EXECUTOR=remote + +# Enable remote execution so actions are performed on the remote systems. +build:remote --remote_executor=grpcs://remote.buildbuddy.io + +# Enforce stricter environment rules, which eliminates some non-hermetic +# behavior and therefore improves both the remote cache hit rate and the +# correctness and repeatability of the build. +build:remote --incompatible_strict_action_env=true + +# Set a higher timeout value, just in case. +build:remote --remote_timeout=3600 diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000000..7cbea073bea --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +5.2.0 \ No newline at end of file diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 00000000000..8f9e7e4eeb7 --- /dev/null +++ b/.github/workflows/bazel.yml @@ -0,0 +1,22 @@ +name: Build and Run Tests By Bazel +on: + pull_request_target: + types: [opened, reopened, synchronize] + push: + branches: + - master + - develop + - bazel +jobs: + build: + name: "Java (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04] + steps: + - uses: actions/checkout@v2 + - name: Build + run: bazel build --config=remote --remote_header=x-buildbuddy-api-key=${{ secrets.BUILD_BUDDY_API_KEY }} //... + - name: Run Tests + run: bazel test --config=remote --remote_header=x-buildbuddy-api-key=${{ secrets.BUILD_BUDDY_API_KEY }} //... \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/maven.yaml similarity index 89% rename from .github/workflows/build.yaml rename to .github/workflows/maven.yaml index 26313b58a7c..66fed022a74 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/maven.yaml @@ -1,9 +1,9 @@ -name: Build +name: Build and Run Tests By Maven on: pull_request: types: [opened, reopened, synchronize] push: - branches: [master, develop] + branches: [master, develop, bazel] jobs: java_build: name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})" diff --git a/.gitignore b/.gitignore index 264f48d0dd7..ad431b3614a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ devenv .DS_Store localbin nohup.out +bazel-out +bazel-bin +bazel-rocketmq +bazel-testlogs +.vscode \ No newline at end of file diff --git a/.licenserc.yaml b/.licenserc.yaml index 3d6205e9304..51741f9032c 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -42,6 +42,7 @@ header: - 'distribution/LICENSE-BIN' - 'distribution/NOTICE-BIN' - 'distribution/conf/rmq-proxy.json' + - '.bazelversion' comment: on-failure \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000000..0663d5774f4 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("@bazel_toolchains//rules/exec_properties:exec_properties.bzl", "create_rbe_exec_properties_dict") + +platform( + name = "custom_platform", + # Inherit from the platform target generated by 'rbe_configs_gen' assuming the generated configs + # were imported as a Bazel external repository named 'rbe_default'. If you extracted the + # generated configs elsewhere in your source repository, replace the following with the label + # to the 'platform' target in the generated configs. + parents = ["@rbe_default//config:platform"], + # Example custom execution property instructing RBE to use e2-standard-2 GCE VMs. + exec_properties = create_rbe_exec_properties_dict( + container_image = "ubuntu:latest", + ), +) + +java_library( + name = "test_deps", + visibility = ["//visibility:public"], + exports = [ + "@maven//:junit_junit", + "@maven//:org_assertj_assertj_core", + "@maven//:org_hamcrest_hamcrest_library", + "@maven//:org_mockito_mockito_core", + "@maven//:org_hamcrest_hamcrest_core", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:org_awaitility_awaitility", + "@maven//:org_openjdk_jmh_jmh_core", + "@maven//:org_openjdk_jmh_jmh_generator_annprocess", + ], +) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000000..735cf6f9a01 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,128 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_JVM_EXTERNAL_TAG = "4.2" + +RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) + +load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") + +rules_jvm_external_deps() + +load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") + +rules_jvm_external_setup() + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + artifacts = [ + "junit:junit:4.13.2", + "com.alibaba:fastjson:1.2.76", + "org.hamcrest:hamcrest-library:1.3", + "io.netty:netty-all:4.1.65.Final", + "org.slf4j:slf4j-api:1.7.7", + "org.assertj:assertj-core:3.22.0", + "org.mockito:mockito-core:3.10.0", + "com.github.luben:zstd-jni:1.5.2-2", + "org.lz4:lz4-java:1.8.0", + "commons-validator:commons-validator:1.7", + "org.apache.commons:commons-lang3:3.4", + "org.hamcrest:hamcrest-core:1.3", + # "io.openmessaging.storage:dledger:0.2.4", + "net.java.dev.jna:jna:4.2.2", + "ch.qos.logback:logback-classic:1.2.10", + "ch.qos.logback:logback-core:1.2.10", + "io.opentracing:opentracing-api:0.33.0", + "io.opentracing:opentracing-mock:0.33.0", + "commons-collections:commons-collections:3.2.2", + "org.awaitility:awaitility:4.1.0", + "commons-cli:commons-cli:1.4", + "com.google.guava:guava:31.0.1-jre", + "org.yaml:snakeyaml:1.30", + "commons-codec:commons-codec:1.13", + "commons-io:commons-io:2.7", + "log4j:log4j:1.2.17", + "com.google.truth:truth:0.30", + "org.bouncycastle:bcpkix-jdk15on:1.69", + "com.google.code.gson:gson:2.8.9", + "com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2", + "org.apache.rocketmq:rocketmq-proto:2.0.0", + "com.google.protobuf:protobuf-java:3.20.1", + "com.google.protobuf:protobuf-java-util:3.20.1", + "com.conversantmedia:disruptor:1.2.10", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.code.findbugs:jsr305:3.0.2", + "org.checkerframework:checker-qual:3.12.0", + "org.reflections:reflections:0.9.11", + "org.openjdk.jmh:jmh-core:1.19", + "org.openjdk.jmh:jmh-generator-annprocess:1.19", + "com.github.ben-manes.caffeine:caffeine:2.9.3", + "io.grpc:grpc-services:1.47.0", + "io.grpc:grpc-netty-shaded:1.47.0", + "io.grpc:grpc-context:1.47.0", + "io.grpc:grpc-stub:1.47.0", + "io.grpc:grpc-api:1.47.0", + "io.grpc:grpc-testing:1.47.0", + ], + fetch_sources = True, + repositories = [ + # Private repositories are supported through HTTP Basic auth + "https://repo1.maven.org/maven2", + ], +) + +http_archive( + name = "io_buildbuddy_buildbuddy_toolchain", + sha256 = "a2a5cccec251211e2221b1587af2ce43c36d32a42f5d881737db3b546a536510", + strip_prefix = "buildbuddy-toolchain-829c8a574f706de5c96c54ca310f139f4acda7dd", + urls = ["https://github.com/buildbuddy-io/buildbuddy-toolchain/archive/829c8a574f706de5c96c54ca310f139f4acda7dd.tar.gz"], +) + +load("@io_buildbuddy_buildbuddy_toolchain//:deps.bzl", "buildbuddy_deps") + +buildbuddy_deps() + +load("@io_buildbuddy_buildbuddy_toolchain//:rules.bzl", "buildbuddy") + +buildbuddy(name = "buildbuddy_toolchain") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rbe_default", + # sha256 = "c0d428774cbe70d477e1d07581d863f8dbff4ba6a66d20502d7118354a814bea", + urls = ["https://storage.googleapis.com/rbe-toolchain/bazel-configs/rbe-ubuntu1604/latest/rbe_default.tar"], +) + +http_archive( + name = "bazel_toolchains", + urls = ["https://github.com/bazelbuild/bazel-toolchains/archive/dac71231098d891e5c4b74a2078fe9343feef510.tar.gz"], + strip_prefix = "bazel-toolchains-dac71231098d891e5c4b74a2078fe9343feef510", + sha256 = "56d5370eb99559b4c74f334f81bc8a298f728bd16d5a4333c865c2ad10fae3bc", +) + +load("@bazel_toolchains//repositories:repositories.bzl", bazel_toolchains_repositories = "repositories") +bazel_toolchains_repositories() diff --git a/acl/BUILD.bazel b/acl/BUILD.bazel new file mode 100644 index 00000000000..11a283ed43f --- /dev/null +++ b/acl/BUILD.bazel @@ -0,0 +1,72 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "acl", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//remoting", + "//logging", + "//srvutil", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:org_yaml_snakeyaml", + "@maven//:commons_codec_commons_codec", + "@maven//:org_apache_rocketmq_rocketmq_proto", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":acl", + "//common", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:org_yaml_snakeyaml", + "@maven//:commons_codec_commons_codec", + "@maven//:com_alibaba_fastjson", + ], + resources = glob(["src/test/resources/**/*.yml"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], + # The following tests are not hermetic. Fix them later. + exclude_tests = [ + "src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest", + "src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest", + "src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest", + ], +) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java index bde50422c2e..69e03523c8e 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java @@ -17,14 +17,11 @@ package org.apache.rocketmq.acl.common; import com.alibaba.fastjson.JSONObject; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileWriter; -import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; -import java.nio.channels.FileChannel; import java.util.Map; import java.util.SortedMap; import org.apache.commons.lang3.StringUtils; @@ -239,92 +236,62 @@ public static String expandIP(String netaddress, int part) { } public static T getYamlDataObject(String path, Class clazz) { + try (FileInputStream fis = new FileInputStream(path)) { + return getYamlDataObject(fis, clazz); + } catch (FileNotFoundException ignore) { + return null; + } catch (Exception e) { + throw new AclException(e.getMessage(), e); + } + } + + public static T getYamlDataObject(InputStream fis, Class clazz) { Yaml yaml = new Yaml(); - FileInputStream fis = null; try { - fis = new FileInputStream(new File(path)); return yaml.loadAs(fis, clazz); - } catch (FileNotFoundException ignore) { - return null; } catch (Exception e) { - throw new AclException(e.getMessage()); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException ignore) { - } - } + throw new AclException(e.getMessage(), e); } } public static boolean writeDataObject(String path, Map dataMap) { Yaml yaml = new Yaml(); - PrintWriter pw = null; - try { - pw = new PrintWriter(new FileWriter(path)); + try (PrintWriter pw = new PrintWriter(new FileWriter(path))) { String dumpAsMap = yaml.dumpAsMap(dataMap); pw.print(dumpAsMap); pw.flush(); } catch (Exception e) { - throw new AclException(e.getMessage()); - } finally { - if (pw != null) { - pw.close(); - } + throw new AclException(e.getMessage(), e); } return true; } - public static boolean copyFile(String from, String to) { - FileChannel input = null; - FileChannel output = null; - try { - input = new FileInputStream(new File(from)).getChannel(); - output = new FileOutputStream(new File(to)).getChannel(); - output.transferFrom(input, 0, input.size()); - return true; - } catch (Exception e) { - log.error("file copy error. from={}, to={}", from, to, e); - } finally { - closeFileChannel(input); - closeFileChannel(output); - } - return false; - } - - public static boolean moveFile(String from, String to) { + public static RPCHook getAclRPCHook(String fileName) { + JSONObject yamlDataObject; try { - File file = new File(from); - return file.renameTo(new File(to)); + yamlDataObject = AclUtils.getYamlDataObject(fileName, + JSONObject.class); } catch (Exception e) { - log.error("file move error. from={}, to={}", from, to, e); - } - return false; - } - - private static void closeFileChannel(FileChannel fileChannel) { - if (fileChannel != null) { - try { - fileChannel.close(); - } catch (IOException e) { - log.error("Close file channel error.", e); - } + log.error("Convert yaml file to data object error, ", e); + return null; } + return buildRpcHook(yamlDataObject); } - public static RPCHook getAclRPCHook(String fileName) { + public static RPCHook getAclRPCHook(InputStream inputStream) { JSONObject yamlDataObject = null; try { - yamlDataObject = AclUtils.getYamlDataObject(fileName, - JSONObject.class); + yamlDataObject = AclUtils.getYamlDataObject(inputStream, JSONObject.class); } catch (Exception e) { log.error("Convert yaml file to data object error, ", e); return null; } + return buildRpcHook(yamlDataObject); + } + private static RPCHook buildRpcHook(JSONObject yamlDataObject) { if (yamlDataObject == null || yamlDataObject.isEmpty()) { - log.warn("Cannot find conf file :{}, acl isn't be enabled.", fileName); + log.warn("Failed to parse configuration to enable ACL."); return null; } @@ -332,8 +299,7 @@ public static RPCHook getAclRPCHook(String fileName) { String secretKey = yamlDataObject.getString(AclConstants.CONFIG_SECRET_KEY); if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey)) { - log.warn("AccessKey or secretKey is blank, the acl is not enabled."); - + log.warn("Failed to enable ACL. Either AccessKey or secretKey is blank"); return null; } return new AclClientRPCHook(new SessionCredentials(accessKey, secretKey)); diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java index 77803062535..f2caf243158 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java @@ -50,18 +50,18 @@ public RemoteAddressStrategy getRemoteAddressStrategy(String remoteAddr) { String[] strArray = StringUtils.split(remoteAddr, ":"); String last = strArray[strArray.length - 1]; if (!last.startsWith("{")) { - throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress: %s", remoteAddr)); } return new MultipleRemoteAddressStrategy(AclUtils.getAddresses(remoteAddr, last)); } else { String[] strArray = StringUtils.split(remoteAddr, "."); // However a right IP String provided by user,it always can be divided into 4 parts by '.'. if (strArray.length < 4) { - throw new AclException(String.format("MultipleRemoteAddressStrategy has got a/some wrong format IP(s) ", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy has got a/some wrong format IP(s): %s ", remoteAddr)); } String lastStr = strArray[strArray.length - 1]; if (!lastStr.startsWith("{")) { - throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress", remoteAddr)); + throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress: %s", remoteAddr)); } return new MultipleRemoteAddressStrategy(AclUtils.getAddresses(remoteAddr, lastStr)); } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java index 850b5335a1d..2956df9c1ed 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java @@ -19,11 +19,15 @@ import com.alibaba.fastjson.JSONObject; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.remoting.RPCHook; import org.junit.Assert; @@ -32,13 +36,10 @@ public class AclUtilsTest { @Test - public void getAddresses() { + public void testGetAddresses() { String address = "1.1.1.{1,2,3,4}"; String[] addressArray = AclUtils.getAddresses(address, "{1,2,3,4}"); - List newAddressList = new ArrayList<>(); - for (String a : addressArray) { - newAddressList.add(a); - } + List newAddressList = new ArrayList<>(Arrays.asList(addressArray)); List addressList = new ArrayList<>(); addressList.add("1.1.1.1"); @@ -47,13 +48,11 @@ public void getAddresses() { addressList.add("1.1.1.4"); Assert.assertEquals(newAddressList, addressList); -// IPv6 test + // IPv6 test String ipv6Address = "1:ac41:9987::bb22:666:{1,2,3,4}"; String[] ipv6AddressArray = AclUtils.getAddresses(ipv6Address, "{1,2,3,4}"); List newIPv6AddressList = new ArrayList<>(); - for (String a : ipv6AddressArray) { - newIPv6AddressList.add(a); - } + Collections.addAll(newIPv6AddressList, ipv6AddressArray); List ipv6AddressList = new ArrayList<>(); ipv6AddressList.add("1:ac41:9987::bb22:666:1"); @@ -64,7 +63,7 @@ public void getAddresses() { } @Test - public void isScopeStringArray() { + public void testIsScope_StringArray() { String address = "12"; for (int i = 0; i < 6; i++) { @@ -79,7 +78,7 @@ public void isScopeStringArray() { } @Test - public void isScopeArray() { + public void testIsScope_Array() { String[] address = StringUtils.split("12.12.12.12", "."); boolean isScope = AclUtils.isScope(address, 4); Assert.assertTrue(isScope); @@ -92,7 +91,7 @@ public void isScopeArray() { isScope = AclUtils.isScope(address, 3); Assert.assertFalse(isScope); -// IPv6 test + // IPv6 test address = StringUtils.split("1050:0000:0000:0000:0005:0600:300c:326b", ":"); isScope = AclUtils.isIPv6Scope(address, 8); Assert.assertTrue(isScope); @@ -110,11 +109,10 @@ public void isScopeArray() { Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(address, 4); Assert.assertTrue(isScope); - } @Test - public void isScopeStringTest() { + public void testIsScope_String() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i + ""); Assert.assertTrue(isScope); @@ -126,7 +124,7 @@ public void isScopeStringTest() { } @Test - public void isScopeTest() { + public void testIsScope_Integral() { for (int i = 0; i < 256; i++) { boolean isScope = AclUtils.isScope(i); Assert.assertTrue(isScope); @@ -136,7 +134,7 @@ public void isScopeTest() { isScope = AclUtils.isScope(256); Assert.assertFalse(isScope); - // IPv6 test + // IPv6 test int min = Integer.parseInt("0", 16); int max = Integer.parseInt("ffff", 16); for (int i = min; i < max + 1; i++) { @@ -147,11 +145,10 @@ public void isScopeTest() { Assert.assertFalse(isScope); isScope = AclUtils.isIPv6Scope(max + 1); Assert.assertFalse(isScope); - } @Test - public void isAsteriskTest() { + public void testIsAsterisk() { boolean isAsterisk = AclUtils.isAsterisk("*"); Assert.assertTrue(isAsterisk); @@ -160,7 +157,7 @@ public void isAsteriskTest() { } @Test - public void isColonTest() { + public void testIsComma() { boolean isColon = AclUtils.isComma(","); Assert.assertTrue(isColon); @@ -169,7 +166,7 @@ public void isColonTest() { } @Test - public void isMinusTest() { + public void testIsMinus() { boolean isMinus = AclUtils.isMinus("-"); Assert.assertTrue(isMinus); @@ -178,30 +175,21 @@ public void isMinusTest() { } @Test - public void v6ipProcessTest() { + public void testV6ipProcess() { String remoteAddr = "5::7:6:1-200:*"; - String[] strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0007:0006"); remoteAddr = "5::7:6:1-200"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0000:0007:0006"); - remoteAddr = "5::7:6:*"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0000:0000:0000:0000:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0000:0000:0000:0000:0007:0006"); remoteAddr = "5:7:6:*"; - strArray = StringUtils.split(remoteAddr, ":"); Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr), "0005:0007:0006"); -// Assert.assertEquals(AclUtils.v6ipProcess(remoteAddr, strArray, 3), "0005:0007:0006"); } @Test - public void expandIPTest() { + public void testExpandIP() { Assert.assertEquals(AclUtils.expandIP("::", 8), "0000:0000:0000:0000:0000:0000:0000:0000"); Assert.assertEquals(AclUtils.expandIP("::1", 8), "0000:0000:0000:0000:0000:0000:0000:0001"); Assert.assertEquals(AclUtils.expandIP("3::", 8), "0003:0000:0000:0000:0000:0000:0000:0000"); @@ -214,19 +202,30 @@ public void expandIPTest() { @SuppressWarnings("unchecked") @Test - public void getYamlDataObjectTest() { + public void testGetYamlDataObject() throws IOException { + try (InputStream is = AclUtilsTest.class.getClassLoader().getResourceAsStream("conf/plain_acl_correct.yml")) { + Map map = AclUtils.getYamlDataObject(is, Map.class); + Assert.assertNotNull(map); + Assert.assertFalse(map.isEmpty()); + } + } - Map map = AclUtils.getYamlDataObject("src/test/resources/conf/plain_acl_correct.yml", Map.class); - Assert.assertFalse(map.isEmpty()); + private static String randomTmpFile() { + String tmpFileName = System.getProperty("java.io.tmpdir"); + // https://rationalpi.wordpress.com/2007/01/26/javaiotmpdir-inconsitency/ + if (!tmpFileName.endsWith(File.separator)) { + tmpFileName += File.separator; + } + tmpFileName += UUID.randomUUID() + ".yml"; + return tmpFileName; } @Test public void writeDataObject2YamlFileTest() throws IOException { - - String targetFileName = "src/test/resources/conf/plain_write_acl.yml"; + String targetFileName = randomTmpFile(); File transport = new File(targetFileName); - transport.delete(); - transport.createNewFile(); + Assert.assertTrue(transport.createNewFile()); + transport.deleteOnExit(); Map aclYamlMap = new HashMap(); @@ -249,17 +248,14 @@ public void writeDataObject2YamlFileTest() throws IOException { accounts.add(accountsMap); aclYamlMap.put("accounts", accounts); Assert.assertTrue(AclUtils.writeDataObject(targetFileName, aclYamlMap)); - - transport.delete(); } @Test public void updateExistedYamlFileTest() throws IOException { - - String targetFileName = "src/test/resources/conf/plain_update_acl.yml"; + String targetFileName = randomTmpFile(); File transport = new File(targetFileName); - transport.delete(); - transport.createNewFile(); + Assert.assertTrue(transport.createNewFile()); + transport.deleteOnExit(); Map aclYamlMap = new HashMap(); @@ -283,53 +279,20 @@ public void updateExistedYamlFileTest() throws IOException { Map readableMap = AclUtils.getYamlDataObject(targetFileName, Map.class); List updatedGlobalWhiteRemoteAddrs = (List) readableMap.get("globalWhiteRemoteAddrs"); Assert.assertEquals("192.168.1.2", updatedGlobalWhiteRemoteAddrs.get(0)); - - transport.delete(); } @Test public void getYamlDataIgnoreFileNotFoundExceptionTest() { JSONObject yamlDataObject = AclUtils.getYamlDataObject("plain_acl.yml", JSONObject.class); - Assert.assertTrue(yamlDataObject == null); - } - - - @Test - public void getAclRPCHookTest() { - - //RPCHook errorContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_format_error.yml"); - //Assert.assertNull(errorContRPCHook); - - RPCHook noFileRPCHook = AclUtils.getAclRPCHook("src/test/resources/plain_acl_format_error1.yml"); - Assert.assertNull(noFileRPCHook); - - //RPCHook emptyContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_null.yml"); - //Assert.assertNull(emptyContRPCHook); - - RPCHook incompleteContRPCHook = AclUtils.getAclRPCHook("src/test/resources/conf/plain_acl_incomplete.yml"); - Assert.assertNull(incompleteContRPCHook); + Assert.assertNull(yamlDataObject); } @Test - public void testCopyAndMoveFile() { - String path = "src/test/resources/conf/plain_acl.yml"; - String backupPath = "src/test/resources/conf/plain_acl.yml_backup"; - Map aclYamlData = AclUtils.getYamlDataObject(path, Map.class); - - AclUtils.copyFile(path, backupPath); - Assert.assertEquals(aclYamlData, AclUtils.getYamlDataObject(backupPath, Map.class)); - - Map aclYamlMap = new HashMap(); - List globalWhiteRemoteAddrs = new ArrayList(); - globalWhiteRemoteAddrs.add("10.10.103.*"); - globalWhiteRemoteAddrs.add("192.168.0.*"); - aclYamlMap.put("globalWhiteRemoteAddrs", globalWhiteRemoteAddrs); - AclUtils.writeDataObject(path, aclYamlMap); - Assert.assertNotEquals(aclYamlData, AclUtils.getYamlDataObject(path, Map.class)); - - AclUtils.moveFile(backupPath, path); - Assert.assertEquals(aclYamlData, AclUtils.getYamlDataObject(path, Map.class)); + public void getAclRPCHookTest() throws IOException { + try (InputStream is = AclUtilsTest.class.getClassLoader().getResourceAsStream("conf/plain_acl_incomplete.yml")) { + RPCHook incompleteContRPCHook = AclUtils.getAclRPCHook(is); + Assert.assertNull(incompleteContRPCHook); + } } - } diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel new file mode 100644 index 00000000000..a9fd83fea0b --- /dev/null +++ b/bazel/BUILD.bazel @@ -0,0 +1,16 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# \ No newline at end of file diff --git a/bazel/GenTestRules.bzl b/bazel/GenTestRules.bzl new file mode 100644 index 00000000000..73cbb4a84ba --- /dev/null +++ b/bazel/GenTestRules.bzl @@ -0,0 +1,109 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Generate java test rules from given test_files. + +Instead of having to create one test rule per test in the BUILD file, this rule +provides a handy way to create a bunch of test rules for the specified test +files. + +""" + +def GenTestRules( + name, + test_files, + deps, + exclude_tests = [], + default_test_size = "small", + small_tests = [], + medium_tests = [], + large_tests = [], + enormous_tests = [], + resources = [], + flaky_tests = [], + tags = [], + prefix = "", + jvm_flags = [], + args = [], + visibility = None, + shard_count = 1): + for test in _get_test_names(test_files): + if test in exclude_tests: + continue + test_size = default_test_size + if test in small_tests: + test_size = "small" + if test in medium_tests: + test_size = "medium" + if test in large_tests: + test_size = "large" + if test in enormous_tests: + test_size = "enormous" + flaky = 0 + if (test in flaky_tests) or ("flaky" in tags): + flaky = 1 + java_class = _package_from_path( + native.package_name() + "/" + _strip_right(test, ".java"), + ) + package = java_class[:java_class.rfind(".")] + native.java_test( + name = prefix + test, + runtime_deps = deps, + resources = resources, + size = test_size, + jvm_flags = jvm_flags, + args = args, + flaky = flaky, + tags = tags, + test_class = java_class, + visibility = visibility, + shard_count = shard_count, + ) + +def _get_test_names(test_files): + test_names = [] + for test_file in test_files: + if not test_file.endswith("Test.java") and not test_file.endswith("IT.java"): + continue + test_names += [test_file[:-5]] + return test_names + +def _package_from_path(package_path, src_impls = None): + src_impls = src_impls or ["javatests/", "java/"] + for src_impl in src_impls: + if not src_impl.endswith("/"): + src_impl += "/" + index = _index_of_end(package_path, src_impl) + if index >= 0: + package_path = package_path[index:] + break + return package_path.replace("/", ".") + +def _strip_right(str, suffix): + """Returns str without the suffix if it ends with suffix.""" + if str.endswith(suffix): + return str[0:len(str) - len(suffix)] + else: + return str + +def _index_of_end(str, part): + """If part is in str, return the index of the first character after part. + Return -1 if part is not in str.""" + index = str.find(part) + if index >= 0: + return index + len(part) + return -1 diff --git a/broker/BUILD.bazel b/broker/BUILD.bazel new file mode 100644 index 00000000000..51afc4f3458 --- /dev/null +++ b/broker/BUILD.bazel @@ -0,0 +1,82 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "broker", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//remoting", + "//logging", + "//common", + "//store", + "//client", + "//filter", + "//srvutil", + "//acl", + "//store:io_openmessaging_storage_dledger", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:org_slf4j_slf4j_api", + "@maven//:commons_cli_commons_cli", + "@maven//:com_google_guava_guava", + "@maven//:com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru", + "@maven//:commons_io_commons_io", + "@maven//:commons_collections_commons_collections", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":broker", + "//acl", + "//client", + "//filter", + "//logging", + "//store", + "//common", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + ], + resources = [ + "src/test/resources/logback-test.xml", + "src/test/resources/META-INF/service/org.apache.rocketmq.acl.AccessValidator", + "src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener", + "src/test/resources/META-INF/service/org.apache.rocketmq.broker.transaction.TransactionalMessageService", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java index 681ca6ef046..92fccf3a416 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -415,7 +415,13 @@ public BlockingQueue getQueryThreadPoolQueue() { protected void initializeRemotingServer() throws CloneNotSupportedException { this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.clientHousekeepingService); NettyServerConfig fastConfig = (NettyServerConfig) this.nettyServerConfig.clone(); - fastConfig.setListenPort(nettyServerConfig.getListenPort() - 2); + + int listeningPort = nettyServerConfig.getListenPort() - 2; + if (listeningPort < 0) { + listeningPort = 0; + } + fastConfig.setListenPort(listeningPort); + this.fastRemotingServer = new NettyRemotingServer(fastConfig, this.clientHousekeepingService); } @@ -1427,12 +1433,19 @@ protected void startBasicService() throws Exception { if (this.remotingServer != null) { this.remotingServer.start(); + + // In test scenarios where it is up to OS to pick up an available port, set the listening port back to config + if (null != nettyServerConfig && 0 == nettyServerConfig.getListenPort()) { + nettyServerConfig.setListenPort(remotingServer.localListenPort()); + } } if (this.fastRemotingServer != null) { this.fastRemotingServer.start(); } + this.storeHost = new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), this.getNettyServerConfig().getListenPort()); + for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) { if (brokerAttachedPlugin != null) { brokerAttachedPlugin.start(); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java index 5c235714a68..01968bac0c8 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java @@ -70,15 +70,12 @@ public abstract class AbstractSendMessageProcessor implements NettyRequestProces protected List consumeMessageHookList; protected final static int DLQ_NUMS_PER_GROUP = 1; + protected final BrokerController brokerController; protected final Random random = new Random(System.currentTimeMillis()); - protected final SocketAddress storeHost; private List sendMessageHookList; - protected final BrokerController brokerController; - public AbstractSendMessageProcessor(final BrokerController brokerController) { this.brokerController = brokerController; - this.storeHost = brokerController.getStoreHost(); } public void registerConsumeMessageHook(List consumeMessageHookList) { @@ -126,7 +123,7 @@ protected RemotingCommand consumerSendMsgBack(final ChannelHandlerContext ctx, f } String newTopic = MixAll.getRetryTopic(requestHeader.getGroup()); - int queueIdInt = Math.abs(this.random.nextInt() % 99999999) % subscriptionGroupConfig.getRetryQueueNums(); + int queueIdInt = this.random.nextInt(subscriptionGroupConfig.getRetryQueueNums()); int topicSysFlag = 0; if (requestHeader.isUnitMode()) { @@ -293,7 +290,7 @@ protected RemotingCommand consumerSendMsgBack(final ChannelHandlerContext ctx, f requestHeader.getOriginTopic(), requestHeader.getGroup(), uniqKey, - putMessageResult == null ? "null" : putMessageResult.getPutMessageStatus().toString()); + "null"); } response.setCode(ResponseCode.SYSTEM_ERROR); @@ -419,7 +416,7 @@ protected MessageExtBrokerInner buildInnerMsg(final ChannelHandlerContext ctx, } public SocketAddress getStoreHost() { - return storeHost; + return brokerController.getStoreHost(); } protected RemotingCommand msgContentCheck(final ChannelHandlerContext ctx, diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java index 18286922f45..75ad961ce9f 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java @@ -18,6 +18,7 @@ package org.apache.rocketmq.broker; import java.io.File; +import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -30,19 +31,37 @@ import org.apache.rocketmq.remoting.netty.RequestTask; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class BrokerControllerTest { + private MessageStoreConfig messageStoreConfig; + + private BrokerConfig brokerConfig; + + private NettyServerConfig nettyServerConfig; + + + @Before + public void setUp() { + messageStoreConfig = new MessageStoreConfig(); + String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + + UUID.randomUUID().toString(); + messageStoreConfig.setStorePathRootDir(storePathRootDir); + + brokerConfig = new BrokerConfig(); + + nettyServerConfig = new NettyServerConfig(); + nettyServerConfig.setListenPort(0); + + } + @Test public void testBrokerRestart() throws Exception { - BrokerController brokerController = new BrokerController( - new BrokerConfig(), - new NettyServerConfig(), - new NettyClientConfig(), - new MessageStoreConfig()); + BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); assertThat(brokerController.initialize()).isTrue(); brokerController.start(); brokerController.shutdown(); @@ -50,16 +69,12 @@ public void testBrokerRestart() throws Exception { @After public void destroy() { - UtilAll.deleteFile(new File(new MessageStoreConfig().getStorePathRootDir())); + UtilAll.deleteFile(new File(messageStoreConfig.getStorePathRootDir())); } @Test public void testHeadSlowTimeMills() throws Exception { - BrokerController brokerController = new BrokerController( - new BrokerConfig(), - new NettyServerConfig(), - new NettyClientConfig(), - new MessageStoreConfig()); + BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, new NettyClientConfig(), messageStoreConfig); brokerController.initialize(); BlockingQueue queue = new LinkedBlockingQueue<>(); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java b/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java index 9445098471f..b14005942ce 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/filter/MessageStoreWithFilterTest.java @@ -61,7 +61,7 @@ public class MessageStoreWithFilterTest { private static final String topic = "topic"; private static final int queueId = 0; - private static final String storePath = "." + File.separator + "unit_test_store"; + private static final String storePath = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; private static final int commitLogFileSize = 1024 * 1024 * 256; private static final int cqFileSize = 300000 * 20; private static final int cqExtFileSize = 300000 * 128; diff --git a/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java index e088753e6ae..61dd6693ef1 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/offset/ConsumerOffsetManagerTest.java @@ -44,12 +44,12 @@ public void init(){ @Test public void cleanOffsetByTopic_NotExist(){ consumerOffsetManager.cleanOffsetByTopic("InvalidTopic"); - assertThat(consumerOffsetManager.getOffsetTable().containsKey(key)); + assertThat(consumerOffsetManager.getOffsetTable().containsKey(key)).isTrue(); } @Test public void cleanOffsetByTopic_Exist(){ consumerOffsetManager.cleanOffsetByTopic("FooBar"); - assertThat(!consumerOffsetManager.getOffsetTable().containsKey(key)); + assertThat(!consumerOffsetManager.getOffsetTable().containsKey(key)).isTrue(); } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java index cff1374b76f..d68797f8f67 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/schedule/ScheduleMessageServiceTest.java @@ -73,7 +73,7 @@ public class ScheduleMessageServiceTest { */ int delayLevel = 3; - private static final String storePath = System.getProperty("user.home") + File.separator + "schedule_test#" + UUID.randomUUID(); + private static final String storePath = System.getProperty("java.io.tmpdir") + File.separator + "schedule_test#" + UUID.randomUUID(); private static final int commitLogFileSize = 1024; private static final int cqFileSize = 10; private static final int cqExtFileSize = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); @@ -113,6 +113,8 @@ public void setUp() throws Exception { messageStoreConfig.setEnableConsumeQueueExt(true); messageStoreConfig.setStorePathRootDir(storePath); messageStoreConfig.setStorePathCommitLog(storePath + File.separator + "commitlog"); + // Let OS pick an available port + messageStoreConfig.setHaListenPort(0); brokerConfig = new BrokerConfig(); BrokerStatsManager manager = new BrokerStatsManager(brokerConfig.getBrokerClusterName(), brokerConfig.isEnableDetailStat()); diff --git a/client/BUILD.bazel b/client/BUILD.bazel new file mode 100644 index 00000000000..c5c57cac09d --- /dev/null +++ b/client/BUILD.bazel @@ -0,0 +1,65 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "client", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//remoting", + "//logging", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:io_opentracing_opentracing_api", + "@maven//:commons_collections_commons_collections", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":client", + "//remoting", + "//common", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:io_opentracing_opentracing_api", + "@maven//:io_opentracing_opentracing_mock", + "@maven//:org_awaitility_awaitility", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], + medium_tests = [ + "src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest", + ], +) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java index cb634375541..7ec8e0033f0 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java @@ -1051,7 +1051,7 @@ public void run() { Validators.checkMessage(msg, defaultMQProducer); if (!msg.getTopic().equals(mq.getTopic())) { - throw new MQClientException("message's topic not equal mq's topic", null); + throw new MQClientException("Topic of the message does not match its target message queue", null); } long costTime = System.currentTimeMillis() - beginStartTime; if (timeout > costTime) { diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java index b5693a556eb..128e4cd8a27 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java @@ -53,6 +53,7 @@ import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.RPCHook; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -93,6 +94,11 @@ public class DefaultLitePullConsumerTest { private String brokerName = "BrokerA"; private boolean flag = false; + @BeforeClass + public static void setEnv() { + System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); + } + @Before public void init() throws Exception { ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java index f7af69b7e71..abc9f93ffd6 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java @@ -161,7 +161,7 @@ public void testConsumerRunningInfoWhenConsumersIsEmptyOrNot() throws RemotingEx runningInfo = mqClientInstance.consumerRunningInfo(group); assertThat(runningInfo).isNotNull(); - assertThat(mockConsumerInner.consumerRunningInfo().getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)); + assertThat(mockConsumerInner.consumerRunningInfo().getProperties().get(ConsumerRunningInfo.PROP_CONSUME_TYPE)).isNotNull(); mqClientInstance.unregisterConsumer(group); flag = mqClientInstance.registerConsumer(group, mock(MQConsumerInner.class)); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java index 884a2f6880e..231f0d04e09 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java @@ -53,6 +53,7 @@ import org.apache.rocketmq.remoting.RPCHook; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -104,6 +105,11 @@ public class DefaultMQLitePullConsumerWithTraceTest { private String customerTraceTopic = "rmq_trace_topic_12345"; + @BeforeClass + public static void setUpEnv() { + System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); + } + @Before public void init() throws Exception { Field field = MQClientInstance.class.getDeclaredField("rebalanceService"); diff --git a/common/BUILD.bazel b/common/BUILD.bazel new file mode 100644 index 00000000000..019d670ce26 --- /dev/null +++ b/common/BUILD.bazel @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "common", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//remoting", + "//logging", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:commons_collections_commons_collections", + "@maven//:commons_codec_commons_codec", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":common", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/common/src/main/java/org/apache/rocketmq/common/MixAll.java b/common/src/main/java/org/apache/rocketmq/common/MixAll.java index 5eca378e543..f8eb4a33af8 100644 --- a/common/src/main/java/org/apache/rocketmq/common/MixAll.java +++ b/common/src/main/java/org/apache/rocketmq/common/MixAll.java @@ -220,7 +220,7 @@ public static String file2String(final File file) throws IOException { } if (result) { - return new String(data); + return new String(data, "UTF-8"); } } return null; diff --git a/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java b/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java index 4f2a341553e..457b9b5fb7a 100644 --- a/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/MixAllTest.java @@ -75,7 +75,7 @@ public void testFile2String_WithChinese() throws IOException { file.delete(); } file.createNewFile(); - PrintWriter out = new PrintWriter(fileName); + PrintWriter out = new PrintWriter(fileName, "UTF-8"); out.write("TestForMixAll_中文"); out.close(); String string = MixAll.file2String(fileName); diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java index 6b0f75acbd8..22e6f3c27a1 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageClientIDSetterTest.java @@ -29,7 +29,7 @@ public void testGetTimeFromID() { long t = System.currentTimeMillis(); String uniqID = MessageClientIDSetter.createUniqID(); long t2 = MessageClientIDSetter.getNearlyTimeFromID(uniqID).getTime(); - assertThat(t2 - t < 20); + assertThat(t2 - t < 20).isTrue(); } @Test @@ -40,7 +40,7 @@ public void testGetCountFromID() { String idHex2 = uniqID2.substring(uniqID2.length() - 4); int s1 = Integer.parseInt(idHex, 16); int s2 = Integer.parseInt(idHex2, 16); - assertThat(s1 == s2 - 1); + assertThat(s1 == s2 - 1).isTrue(); } diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java index ab5fafa18bd..46469e7f889 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java @@ -257,6 +257,7 @@ public void testEncodeAndDecodeOnIPv6Host() { assertThat("abc").isEqualTo(decodedMsg.getTopic()); } + @Test public void testNullValueProperty() throws Exception { MessageExt msg = new MessageExt(); msg.setBody("x".getBytes()); diff --git a/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java b/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java index 677c15fb6f7..117faf5b19d 100644 --- a/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/utils/IOTinyUtilsTest.java @@ -32,7 +32,10 @@ public class IOTinyUtilsTest { - private String testRootDir = System.getProperty("user.home") + File.separator + "iotinyutilstest"; + /** + * https://bazel.build/reference/test-encyclopedia#filesystem + */ + private String testRootDir = System.getProperty("java.io.tmpdir") + File.separator + "iotinyutilstest"; @Before public void init() { @@ -50,7 +53,6 @@ public void destroy() { UtilAll.deleteFile(file); } - @Test public void testToString() throws Exception { byte[] b = "testToString".getBytes(RemotingHelper.DEFAULT_CHARSET); @@ -150,7 +152,7 @@ public void testDelete() throws Exception { @Test public void testCopyFile() throws Exception { - File source = new File(testRootDir, "soruce"); + File source = new File(testRootDir, "source"); String target = testRootDir + File.separator + "dest"; IOTinyUtils.writeStringToFile(source, "testCopyFile", RemotingHelper.DEFAULT_CHARSET); diff --git a/container/BUILD.bazel b/container/BUILD.bazel new file mode 100644 index 00000000000..8986f7ae4d7 --- /dev/null +++ b/container/BUILD.bazel @@ -0,0 +1,82 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "container", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//broker", + "//common", + "//logging", + "//remoting", + "//client", + "//srvutil", + "//store", + "//store:io_openmessaging_storage_dledger", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:commons_collections_commons_collections", + "@maven//:commons_codec_commons_codec", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_core", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:commons_cli_commons_cli", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":container", + "//broker", + "//common", + "//logging", + "//remoting", + "//client", + "//srvutil", + "//store", + "//store:io_openmessaging_storage_dledger", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +# The following tests are flaky, fix them later. + exclude_tests = [ + "src/test/java/org/apache/rocketmq/container/BrokerContainerStartupTest", + "src/test/java/org/apache/rocketmq/container/BrokerContainerTest", + ], +) diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel new file mode 100644 index 00000000000..0e1cc91697b --- /dev/null +++ b/controller/BUILD.bazel @@ -0,0 +1,76 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "controller", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//logging", + "//remoting", + "//client", + "//srvutil", + "//store:io_openmessaging_storage_dledger", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:commons_collections_commons_collections", + "@maven//:commons_codec_commons_codec", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_core", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:commons_cli_commons_cli", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":controller", + "//common", + "//logging", + "//remoting", + "//client", + "//srvutil", + "//store:io_openmessaging_storage_dledger", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], + medium_tests = [ + "src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest", + ], +) diff --git a/controller/src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest.java index dce3167eff7..11d89ef6ef3 100644 --- a/controller/src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest.java +++ b/controller/src/test/java/org/apache/rocketmq/controller/impl/controller/impl/DLedgerControllerTest.java @@ -54,8 +54,9 @@ public class DLedgerControllerTest { private List baseDirs; private List controllers; - public DLedgerController launchController(final String group, final String peers, final String selfId, String storeType, final boolean isEnableElectUncleanMaster) { - final String path = "/tmp" + File.separator + group + File.separator + selfId; + public DLedgerController launchController(final String group, final String peers, final String selfId, + String storeType, final boolean isEnableElectUncleanMaster) { + final String path = System.getProperty("java.io.tmpdir") + File.separator + group + File.separator + selfId; baseDirs.add(path); final ControllerConfig config = new ControllerConfig(); diff --git a/filter/BUILD.bazel b/filter/BUILD.bazel new file mode 100644 index 00000000000..7b6c8687b57 --- /dev/null +++ b/filter/BUILD.bazel @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "filter", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//remoting", + "//logging", + "//srvutil", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":filter", + "//common", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/logging/BUILD.bazel b/logging/BUILD.bazel new file mode 100644 index 00000000000..a2380e71e77 --- /dev/null +++ b/logging/BUILD.bazel @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +java_library( + name = "logging", + srcs = glob(["src/main/java/**/*.java"]), + deps = [ + "@maven//:org_slf4j_slf4j_api", + ], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/namesrv/BUILD.bazel b/namesrv/BUILD.bazel new file mode 100644 index 00000000000..d6efa8c7f38 --- /dev/null +++ b/namesrv/BUILD.bazel @@ -0,0 +1,76 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "namesrv", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//remoting", + "//logging", + "//srvutil", + "//tools", + "//client", + "//common", + "//controller", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:ch_qos_logback_logback_core", + "@maven//:org_slf4j_slf4j_api", + "@maven//:org_bouncycastle_bcpkix_jdk15on", + "@maven//:commons_cli_commons_cli", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":namesrv", + "//remoting", + "//logging", + "//srvutil", + "//tools", + "//client", + "//common", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_cli_commons_cli", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_core", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java index 9c96917c174..827c9ce61c7 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java @@ -250,6 +250,11 @@ public void start() throws Exception { this.remotingServer.start(); this.remotingClient.start(); + // In test scenarios where it is up to OS to pick up an available port, set the listening port back to config + if (0 == nettyServerConfig.getListenPort()) { + nettyServerConfig.setListenPort(this.remotingServer.localListenPort()); + } + if (this.fileWatchService != null) { this.fileWatchService.start(); } diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java index 8cc49caa9ea..54a6320bf9c 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvStartup.java @@ -18,10 +18,8 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.spi.JoranException; import java.io.BufferedInputStream; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.concurrent.Callable; @@ -84,7 +82,7 @@ public static ControllerManager controllerManagerMain() { return null; } - public static void parseCommandlineAndConfigFile(String[] args) throws IOException, JoranException { + public static void parseCommandlineAndConfigFile(String[] args) throws Exception { System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION)); //PackageConflictDetect.detectFastjson(); diff --git a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java index 83ab103f66d..7768272f312 100644 --- a/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java +++ b/namesrv/src/test/java/org/apache/rocketmq/namesrv/NameServerInstanceTest.java @@ -20,6 +20,7 @@ import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.junit.After; import org.junit.Before; +import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -37,6 +38,14 @@ public void startup() throws Exception { nameSrvController.start(); } + /** + * Add a placeholder to make Bazel happy. + */ + @Test + public void itWorks() { + + } + @After public void shutdown() throws Exception { if (nameSrvController != null) { diff --git a/proxy/BUILD.bazel b/proxy/BUILD.bazel new file mode 100644 index 00000000000..eba69215750 --- /dev/null +++ b/proxy/BUILD.bazel @@ -0,0 +1,97 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "proxy", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//remoting", + "//logging", + "//common", + "//client", + "//broker", + "//acl", + "@maven//:org_apache_rocketmq_rocketmq_proto", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:commons_collections_commons_collections", + "@maven//:commons_codec_commons_codec", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_github_ben_manes_caffeine_caffeine", + "@maven//:io_grpc_grpc_services", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:io_grpc_grpc_netty_shaded", + "@maven//:io_grpc_grpc_context", + "@maven//:io_grpc_grpc_stub", + "@maven//:io_grpc_grpc_api", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_core", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:org_checkerframework_checker_qual", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":proxy", + "//remoting", + "//broker", + "//client", + "//common", + "//:test_deps", + "@maven//:org_apache_rocketmq_rocketmq_proto", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_core", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + "@maven//:com_alibaba_fastjson", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:io_grpc_grpc_netty_shaded", + "@maven//:io_grpc_grpc_context", + "@maven//:io_grpc_grpc_stub", + "@maven//:io_grpc_grpc_api", + ], + resources = [ + "src/test/resources/rmq-proxy-home/conf/broker.conf", + "src/test/resources/rmq-proxy-home/conf/rmq-proxy.json", + "src/test/resources/rmq-proxy-home/conf/logback_proxy.xml", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], + exclude_tests = [ + "src/test/java/org/apache/rocketmq/proxy/config/InitConfigAndLoggerTest", + ], +) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java index cf0b715936c..6b62ecb97d2 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java @@ -18,7 +18,11 @@ package org.apache.rocketmq.proxy.config; import com.alibaba.fastjson.JSON; +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.file.Files; import java.util.concurrent.atomic.AtomicReference; import org.apache.rocketmq.common.constant.LoggerName; @@ -41,6 +45,13 @@ public void init() throws Exception { } public static String loadJsonConfig(String configFileName) throws Exception { + final String testResource = "rmq-proxy-home/conf/" + configFileName; + try (InputStream inputStream = Configuration.class.getClassLoader().getResourceAsStream(testResource)) { + if (null != inputStream) { + return CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); + } + } + String filePath = new File(ConfigurationManager.getProxyHome() + File.separator + "conf", configFileName).toString(); File file = new File(filePath); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java index 85a85a50aaf..3943b339272 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java @@ -77,8 +77,7 @@ protected static Settings mergeProducerData(Settings settings) { .setInitial(Durations.fromMillis(config.getGrpcClientProducerBackoffInitialMillis())) .setMax(Durations.fromMillis(config.getGrpcClientProducerBackoffMaxMillis())) .setMultiplier(config.getGrpcClientProducerBackoffMultiplier()) - .build()) - .build(); + .build()); builder.getPublishingBuilder() .setValidateMessageType(config.isEnableTopicMessageTypeCheck()) diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigAndLoggerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigAndLoggerTest.java index 12e8dd74f61..e8442218094 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigAndLoggerTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/config/InitConfigAndLoggerTest.java @@ -22,6 +22,10 @@ import ch.qos.logback.core.joran.spi.JoranException; import java.net.URL; import org.apache.rocketmq.client.log.ClientLogger; +import org.assertj.core.util.Strings; + +import java.io.IOException; +import java.io.InputStream; import org.junit.After; import org.junit.Before; import org.slf4j.LoggerFactory; @@ -37,7 +41,11 @@ public void before() throws Throwable { if (mockProxyHomeURL != null) { mockProxyHome = mockProxyHomeURL.toURI().getPath(); } - System.setProperty(RMQ_PROXY_HOME, mockProxyHome); + + if (!Strings.isNullOrEmpty(mockProxyHome)) { + System.setProperty(RMQ_PROXY_HOME, mockProxyHome); + } + ConfigurationManager.initEnv(); ConfigurationManager.intConfig(); initLogger(); @@ -56,8 +64,18 @@ private static void initLogger() throws JoranException { JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(lc); lc.reset(); - //https://logback.qos.ch/manual/configuration.html + // https://logback.qos.ch/manual/configuration.html lc.setPackagingDataEnabled(false); + + try (InputStream inputStream = InitConfigAndLoggerTest.class.getClassLoader() + .getResourceAsStream("rmq-proxy-home/conf/logback_proxy.xml")) { + if (null != inputStream) { + configurator.doConfigure(inputStream); + return; + } + } catch (IOException ignore) { + } + configurator.doConfigure(mockProxyHome + "/conf/logback_proxy.xml"); } } \ No newline at end of file diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java index 350f5802a32..64b5586008c 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplicationTest.java @@ -34,6 +34,7 @@ import org.apache.rocketmq.proxy.config.InitConfigAndLoggerTest; import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants; import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,9 +78,10 @@ public void testQueryRoute() { metadata.put(InterceptorConstants.LANGUAGE, JAVA); metadata.put(InterceptorConstants.REMOTE_ADDRESS, REMOTE_ADDR); metadata.put(InterceptorConstants.LOCAL_ADDRESS, LOCAL_ADDR); - Context.current() - .withValue(InterceptorConstants.METADATA, metadata) - .attach(); + + Assert.assertNotNull(Context.current() + .withValue(InterceptorConstants.METADATA, metadata) + .attach()); CompletableFuture future = new CompletableFuture<>(); QueryRouteRequest request = QueryRouteRequest.newBuilder() @@ -105,9 +107,10 @@ public void testQueryRouteWithBadClientID() { metadata.put(InterceptorConstants.LANGUAGE, JAVA); metadata.put(InterceptorConstants.REMOTE_ADDRESS, REMOTE_ADDR); metadata.put(InterceptorConstants.LOCAL_ADDRESS, LOCAL_ADDR); - Context.current() - .withValue(InterceptorConstants.METADATA, metadata) - .attach(); + + Assert.assertNotNull(Context.current() + .withValue(InterceptorConstants.METADATA, metadata) + .attach()); QueryRouteRequest request = QueryRouteRequest.newBuilder() .setEndpoints(grpcEndpoints) diff --git a/remoting/BUILD.bazel b/remoting/BUILD.bazel new file mode 100644 index 00000000000..0691bc7d1df --- /dev/null +++ b/remoting/BUILD.bazel @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "remoting", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//logging", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":remoting", + "//:test_deps", + "@maven//:io_netty_netty_all", + "@maven//:com_google_code_gson_gson", + "@maven//:com_alibaba_fastjson", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java index 08f3b107b5f..2d06147dd58 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java @@ -175,8 +175,7 @@ public Thread newThread(Runnable r) { private static int initValueIndex() { Random r = new Random(); - - return Math.abs(r.nextInt() % 999) % 999; + return r.nextInt(999); } @Override diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java index 137ccaca7b4..c3deb8a48b5 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java @@ -80,13 +80,11 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti private DefaultEventExecutorGroup defaultEventExecutorGroup; /** - * NettyRemotingServer may holds multiple SubRemotingServer, each server will be stored in this container with a + * NettyRemotingServer may hold multiple SubRemotingServer, each server will be stored in this container with a * ListenPort key. */ private ConcurrentMap remotingServerTable = new ConcurrentHashMap(); - private int port = 0; - private static final String HANDSHAKE_HANDLER_NAME = "handshakeHandler"; private static final String TLS_HANDLER_NAME = "sslHandler"; private static final String FILE_REGION_ENCODER_NAME = "fileRegionEncoder"; @@ -163,8 +161,6 @@ public Thread newThread(Runnable r) { } loadSslContext(); - - this.remotingServerTable.put(this.nettyServerConfig.getListenPort(), this); } public void loadSslContext() { @@ -247,9 +243,13 @@ public void initChannel(SocketChannel ch) throws Exception { } try { - ChannelFuture sync = this.serverBootstrap.bind().sync(); + ChannelFuture sync = this.serverBootstrap.bind(nettyServerConfig.getListenPort()).sync(); InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress(); - this.port = addr.getPort(); + if (0 == nettyServerConfig.getListenPort()) { + this.nettyServerConfig.setListenPort(addr.getPort()); + log.debug("Server is listening {}", this.nettyServerConfig.getListenPort()); + } + this.remotingServerTable.put(this.nettyServerConfig.getListenPort(), this); } catch (InterruptedException e1) { throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1); } @@ -320,7 +320,7 @@ public void registerDefaultProcessor(NettyRequestProcessor processor, ExecutorSe @Override public int localListenPort() { - return this.port; + return this.nettyServerConfig.getListenPort(); } @Override @@ -540,7 +540,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E * resources from its parent server. */ class SubRemotingServer extends NettyRemotingAbstract implements RemotingServer { - private final int listenPort; + private volatile int listenPort; private volatile Channel serverChannel; SubRemotingServer(final int port, final int permitsOnway, final int permitsAsync) { @@ -613,7 +613,14 @@ public void invokeOneway(final Channel channel, final RemotingCommand request, @Override public void start() { try { + if (listenPort < 0) { + listenPort = 0; + } this.serverChannel = NettyRemotingServer.this.serverBootstrap.bind(listenPort).sync().channel(); + if (0 == listenPort) { + InetSocketAddress addr = (InetSocketAddress) this.serverChannel.localAddress(); + this.listenPort = addr.getPort(); + } } catch (InterruptedException e) { throw new RuntimeException("this.subRemotingServer.serverBootstrap.bind().sync() InterruptedException", e); } diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java index 78c1e1d8118..fd1fe9d0936 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java @@ -17,7 +17,7 @@ package org.apache.rocketmq.remoting.netty; public class NettyServerConfig implements Cloneable { - private int listenPort = 8888; + private int listenPort = 0; private int serverWorkerThreads = 8; private int serverCallbackExecutorThreads = 0; private int serverSelectorThreads = 3; diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java index 130092dedc8..6fa230adb08 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java @@ -34,6 +34,7 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class RemotingServerTest { @@ -90,8 +91,8 @@ public void testInvokeSync() throws InterruptedException, RemotingConnectExcepti requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); RemotingCommand request = RemotingCommand.createRequestCommand(0, requestHeader); - RemotingCommand response = remotingClient.invokeSync("localhost:8888", request, 1000 * 3); - assertTrue(response != null); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); + assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); @@ -103,7 +104,7 @@ public void testInvokeOneway() throws InterruptedException, RemotingConnectExcep RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); - remotingClient.invokeOneway("localhost:8888", request, 1000 * 3); + remotingClient.invokeOneway("localhost:" + remotingServer.localListenPort(), request, 1000 * 3); } @Test @@ -113,7 +114,7 @@ public void testInvokeAsync() throws InterruptedException, RemotingConnectExcept final CountDownLatch latch = new CountDownLatch(1); RemotingCommand request = RemotingCommand.createRequestCommand(0, null); request.setRemark("messi"); - remotingClient.invokeAsync("localhost:8888", request, 1000 * 3, new InvokeCallback() { + remotingClient.invokeAsync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3, new InvokeCallback() { @Override public void operationComplete(ResponseFuture responseFuture) { latch.countDown(); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java index c3f45963456..b1594d0c167 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/SubRemotingServerTest.java @@ -57,7 +57,7 @@ public static RemotingServer createSubRemotingServer(RemotingServer parentServer subServer.registerProcessor(1, new NettyRequestProcessor() { @Override public RemotingCommand processRequest(final ChannelHandlerContext ctx, - final RemotingCommand request) throws Exception { + final RemotingCommand request) throws Exception { request.setRemark(String.valueOf(RemotingHelper.parseSocketAddressPort(ctx.channel().localAddress()))); return request; } @@ -72,14 +72,16 @@ public boolean rejectRequest() { } @Test - public void testInvokeSubRemotingServer() throws InterruptedException, RemotingTimeoutException, RemotingConnectException, RemotingSendRequestException { + public void testInvokeSubRemotingServer() throws InterruptedException, RemotingTimeoutException, + RemotingConnectException, RemotingSendRequestException { RequestHeader requestHeader = new RequestHeader(); requestHeader.setCount(1); requestHeader.setMessageTitle("Welcome"); // Parent remoting server doesn't support RequestCode 1 RemotingCommand request = RemotingCommand.createRequestCommand(1, requestHeader); - RemotingCommand response = remotingClient.invokeSync("localhost:8888", request, 1000 * 3); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), request, + 1000 * 3); assertThat(response).isNotNull(); assertThat(response.getCode()).isEqualTo(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED); diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java index 13bb17282e3..13121526d4b 100644 --- a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java +++ b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java @@ -19,9 +19,14 @@ import java.io.BufferedWriter; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.util.UUID; +import java.io.InputStream; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; @@ -65,7 +70,7 @@ import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; @RunWith(MockitoJUnitRunner.class) public class TlsTest { @@ -198,7 +203,7 @@ public void serverAcceptsUnAuthClient() throws Exception { @Test public void serverRejectsSSLClient() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -211,7 +216,7 @@ public void serverRejectsSSLClient() throws Exception { @Test public void serverRejectsUntrustedClientCert() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), createRequest(), 1000 * 5); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -229,7 +234,7 @@ public void serverAcceptsUntrustedClientCert() throws Exception { @Test public void noClientAuthFailure() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -242,7 +247,7 @@ public void noClientAuthFailure() throws Exception { @Test public void clientRejectsUntrustedServerCert() throws Exception { try { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), createRequest(), 1000 * 3); failBecauseExceptionWasNotThrown(RemotingSendRequestException.class); } catch (RemotingSendRequestException ignore) { } @@ -301,8 +306,31 @@ private static void writeStringToFile(String path, String content) { } private static String getCertsPath(String fileName) { - File resourcesDirectory = new File("src/test/resources/certs"); - return resourcesDirectory.getAbsolutePath() + "/" + fileName; + ClassLoader loader = TlsTest.class.getClassLoader(); + InputStream stream = loader.getResourceAsStream("certs/" + fileName); + if (null == stream) { + throw new RuntimeException("File: " + fileName + " is not found"); + } + + try { + String[] segments = fileName.split("\\."); + File f = File.createTempFile(UUID.randomUUID().toString(), segments[1]); + f.deleteOnExit(); + + try (BufferedInputStream bis = new BufferedInputStream(stream); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f))) { + byte[] buffer = new byte[1024]; + int len; + while ((len = bis.read(buffer)) > 0) { + bos.write(buffer, 0, len); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return f.getAbsolutePath(); + } catch (IOException e) { + throw new RuntimeException(e); + } } private static RemotingCommand createRequest() { @@ -317,8 +345,8 @@ private void requestThenAssertResponse() throws Exception { } private void requestThenAssertResponse(RemotingClient remotingClient) throws Exception { - RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3); - assertTrue(response != null); + RemotingCommand response = remotingClient.invokeSync("localhost:" + remotingServer.localListenPort(), createRequest(), 1000 * 3); + assertNotNull(response); assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA); assertThat(response.getExtFields()).hasSize(2); assertThat(response.getExtFields().get("messageTitle")).isEqualTo("Welcome"); diff --git a/srvutil/BUILD.bazel b/srvutil/BUILD.bazel new file mode 100644 index 00000000000..9f6fcc22ea7 --- /dev/null +++ b/srvutil/BUILD.bazel @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "srvutil", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//common", + "//remoting", + "//logging", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:commons_cli_commons_cli", + "@maven//:com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":srvutil", + "//common", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/srvutil/src/main/test/org/apache/rocketmq/srvutil/FileWatchServiceTest.java b/srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java similarity index 100% rename from srvutil/src/main/test/org/apache/rocketmq/srvutil/FileWatchServiceTest.java rename to srvutil/src/test/java/org/apache/rocketmq/srvutil/FileWatchServiceTest.java diff --git a/store/BUILD.bazel b/store/BUILD.bazel new file mode 100644 index 00000000000..fef1404de60 --- /dev/null +++ b/store/BUILD.bazel @@ -0,0 +1,80 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_import( + name = "io_openmessaging_storage_dledger", + jars = [ + "libs/dledger-0.2.8-SNAPSHOT.jar", + ], + visibility = ["//visibility:public"], +) + +java_library( + name = "store", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//logging", + "//common", + "//remoting", + # "@maven//:io_openmessaging_storage_dledger", + ":io_openmessaging_storage_dledger", + "@maven//:net_java_dev_jna_jna", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_collections_commons_collections", + "@maven//:com_conversantmedia_disruptor", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":store", + "//:test_deps", + "//logging", + "//common", + # "@maven//:io_openmessaging_storage_dledger", + ":io_openmessaging_storage_dledger", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:com_conversantmedia_disruptor", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], + exclude_tests = [ + "src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest", + "src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest", + "src/test/java/org/apache/rocketmq/store/MappedFileQueueTest", + "src/test/java/org/apache/rocketmq/store/queue/BatchConsumeMessageTest", + ], + medium_tests = [ + "src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest", + "src/test/java/org/apache/rocketmq/store/HATest", + ], +) diff --git a/store/libs/dledger-0.2.8-SNAPSHOT.jar b/store/libs/dledger-0.2.8-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..d6919de5831ae0918e6cfb2721e300203fea5813 GIT binary patch literal 186014 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 literal 0 HcmV?d00001 diff --git a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java index 488e5b066e0..9b69e75d91f 100644 --- a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java +++ b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java @@ -707,6 +707,10 @@ public int getHaListenPort() { } public void setHaListenPort(int haListenPort) { + if (haListenPort < 0) { + this.haListenPort = 0; + return; + } this.haListenPort = haListenPort; } diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java index aa307a0af6b..8408b7608c3 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/DefaultHAService.java @@ -38,6 +38,7 @@ import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; import org.apache.rocketmq.store.config.BrokerRole; +import org.apache.rocketmq.store.config.MessageStoreConfig; public class DefaultHAService implements HAService { @@ -66,8 +67,7 @@ public DefaultHAService() { @Override public void init(final DefaultMessageStore defaultMessageStore) throws IOException { this.defaultMessageStore = defaultMessageStore; - this.acceptSocketService = - new DefaultAcceptSocketService(defaultMessageStore.getMessageStoreConfig().getHaListenPort()); + this.acceptSocketService = new DefaultAcceptSocketService(defaultMessageStore.getMessageStoreConfig()); this.groupTransferService = new GroupTransferService(this, defaultMessageStore); if (this.defaultMessageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) { this.haClient = new DefaultHAClient(this.defaultMessageStore); @@ -258,8 +258,8 @@ public HARuntimeInfo getRuntimeInfo(long masterPutWhere) { class DefaultAcceptSocketService extends AcceptSocketService { - public DefaultAcceptSocketService(int port) { - super(port); + public DefaultAcceptSocketService(final MessageStoreConfig messageStoreConfig) { + super(messageStoreConfig); } @Override @@ -284,8 +284,11 @@ protected abstract class AcceptSocketService extends ServiceThread { private ServerSocketChannel serverSocketChannel; private Selector selector; - public AcceptSocketService(final int port) { - this.socketAddressListen = new InetSocketAddress(port); + private final MessageStoreConfig messageStoreConfig; + + public AcceptSocketService(final MessageStoreConfig messageStoreConfig) { + this.messageStoreConfig = messageStoreConfig; + this.socketAddressListen = new InetSocketAddress(messageStoreConfig.getHaListenPort()); } /** @@ -298,6 +301,10 @@ public void beginAccept() throws Exception { this.selector = RemotingUtil.openSelector(); this.serverSocketChannel.socket().setReuseAddress(true); this.serverSocketChannel.socket().bind(this.socketAddressListen); + if (0 == messageStoreConfig.getHaListenPort()) { + messageStoreConfig.setHaListenPort(this.serverSocketChannel.socket().getLocalPort()); + log.info("OS picked up {} to listen for HA", messageStoreConfig.getHaListenPort()); + } this.serverSocketChannel.configureBlocking(false); this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT); } @@ -309,8 +316,13 @@ public void beginAccept() throws Exception { public void shutdown(final boolean interrupt) { super.shutdown(interrupt); try { - this.serverSocketChannel.close(); - this.selector.close(); + if (null != this.serverSocketChannel) { + this.serverSocketChannel.close(); + } + + if (null != this.selector) { + this.selector.close(); + } } catch (IOException e) { log.error("AcceptSocketService shutdown exception", e); } diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java index 2f9e93c0652..74de4d69152 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java @@ -39,6 +39,7 @@ import org.apache.rocketmq.store.DispatchRequest; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.BrokerRole; +import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.store.ha.DefaultHAService; import org.apache.rocketmq.store.ha.GroupTransferService; import org.apache.rocketmq.store.ha.HAClient; @@ -69,7 +70,7 @@ public void init(final DefaultMessageStore defaultMessageStore) throws IOExcepti this.epochCache = new EpochFileCache(defaultMessageStore.getMessageStoreConfig().getStorePathEpochFile()); this.epochCache.initCacheFromFile(); this.defaultMessageStore = defaultMessageStore; - this.acceptSocketService = new AutoSwitchAcceptSocketService(defaultMessageStore.getMessageStoreConfig().getHaListenPort()); + this.acceptSocketService = new AutoSwitchAcceptSocketService(defaultMessageStore.getMessageStoreConfig()); this.groupTransferService = new GroupTransferService(this, defaultMessageStore); this.haConnectionStateNotificationService = new HAConnectionStateNotificationService(this, defaultMessageStore); } @@ -403,8 +404,8 @@ public List getEpochEntries() { class AutoSwitchAcceptSocketService extends AcceptSocketService { - public AutoSwitchAcceptSocketService(int port) { - super(port); + public AutoSwitchAcceptSocketService(final MessageStoreConfig messageStoreConfig) { + super(messageStoreConfig); } @Override diff --git a/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java b/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java index 9dc8a8b2495..0ebd9314bf6 100644 --- a/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/AppendCallbackTest.java @@ -53,8 +53,8 @@ public void init() throws Exception { messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "unitteststore"); - messageStoreConfig.setStorePathCommitLog(System.getProperty("user.home") + File.separator + "unitteststore" + File.separator + "commitlog"); + messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore"); + messageStoreConfig.setStorePathCommitLog(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore" + File.separator + "commitlog"); //too much reference DefaultMessageStore messageStore = new DefaultMessageStore(messageStoreConfig, null, null, new BrokerConfig()); CommitLog commitLog = new CommitLog(messageStore); @@ -63,7 +63,7 @@ public void init() throws Exception { @After public void destroy() { - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "unitteststore")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore")); } @Test diff --git a/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java b/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java index 7d2a6f747c0..c3bf9d53709 100644 --- a/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/BatchPutMessageTest.java @@ -64,7 +64,7 @@ public void destroy() { messageStore.shutdown(); messageStore.destroy(); - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "putmessagesteststore")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore")); } private MessageStore buildMessageStore() throws Exception { @@ -75,8 +75,10 @@ private MessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxIndexNum(100 * 10); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "putmessagesteststore"); - messageStoreConfig.setStorePathCommitLog(System.getProperty("user.home") + File.separator + "putmessagesteststore" + File.separator + "commitlog"); + messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "putmessagesteststore"); + messageStoreConfig.setStorePathCommitLog(System.getProperty("java.io.tmpdir") + File.separator + + "putmessagesteststore" + File.separator + "commitlog"); + messageStoreConfig.setHaListenPort(0); return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig()); } diff --git a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java index 97afee80cd9..6bbe40dae3c 100644 --- a/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/ConsumeQueueTest.java @@ -46,7 +46,7 @@ public class ConsumeQueueTest { private static final String topic = "abc"; private static final int queueId = 0; - private static final String storePath = "." + File.separator + "unit_test_store"; + private static final String storePath = System.getProperty("java.io.tmpdir") + File.separator + "unit_test_store"; private static final int commitLogFileSize = 1024 * 8; private static final int cqFileSize = 10 * 20; private static final int cqExtFileSize = 10 * (ConsumeQueueExt.CqExtUnit.MIN_EXT_UNIT_SIZE + 64); @@ -119,7 +119,7 @@ public MessageStoreConfig buildStoreConfig(int commitLogFileSize, int cqFileSize messageStoreConfig.setMappedFileSizeConsumeQueueExt(cqExtFileSize); messageStoreConfig.setMessageIndexEnable(false); messageStoreConfig.setEnableConsumeQueueExt(enableCqExt); - + messageStoreConfig.setHaListenPort(0); messageStoreConfig.setStorePathRootDir(storePath); messageStoreConfig.setStorePathCommitLog(storePath + File.separator + "commitlog"); diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index 2485b2a3a1b..601d50c0f52 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -473,7 +473,7 @@ private MessageStoreConfig genMessageStoreConfig(String deleteWhen, int diskMaxU messageStoreConfig.setDeleteWhen(deleteWhen); messageStoreConfig.setDiskMaxUsedSpaceRatio(diskMaxUsedSpaceRatio); - String storePathRootDir = System.getProperty("user.home") + File.separator + String storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "DefaultMessageStoreCleanFilesTest-" + UUID.randomUUID(); String storePathCommitLog = storePathRootDir + File.separator + "commitlog"; messageStoreConfig.setStorePathRootDir(storePathRootDir); diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java index 21707597e4c..7329098a38e 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreShutDownTest.java @@ -70,7 +70,10 @@ public DefaultMessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxHashSlotNum(10000); messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); - messageStoreConfig.setHaListenPort(StoreTestBase.nextPort()); + messageStoreConfig.setHaListenPort(0); + String storeRootPath = System.getProperty("java.io.tmpdir") + File.separator + "store"; + messageStoreConfig.setStorePathRootDir(storeRootPath); + messageStoreConfig.setHaListenPort(0); return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), null, new BrokerConfig()); } diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java index db9b3c665f4..09d507395f3 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.common.BrokerConfig; @@ -49,6 +50,7 @@ import org.apache.rocketmq.store.queue.ConsumeQueueInterface; import org.apache.rocketmq.store.queue.CqUnit; import org.apache.rocketmq.store.stats.BrokerStatsManager; +import org.assertj.core.util.Strings; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -90,6 +92,8 @@ public void test_repeat_restart() throws Exception { messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); + messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "store"); + messageStoreConfig.setHaListenPort(0); MessageStore master = new DefaultMessageStore(messageStoreConfig, null, new MyMessageArrivingListener(), new BrokerConfig()); boolean load = master.load(); @@ -115,6 +119,10 @@ public void destroy() { } private MessageStore buildMessageStore() throws Exception { + return buildMessageStore(null); + } + + private MessageStore buildMessageStore(String storePathRootDir) throws Exception { MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10); messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10); @@ -122,8 +130,16 @@ private MessageStore buildMessageStore() throws Exception { messageStoreConfig.setMaxIndexNum(100 * 100); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); - messageStoreConfig.setHaListenPort(StoreTestBase.nextPort()); - return new DefaultMessageStore(messageStoreConfig, new BrokerStatsManager("simpleTest", true), new MyMessageArrivingListener(), new BrokerConfig()); + messageStoreConfig.setHaListenPort(0); + if (Strings.isNullOrEmpty(storePathRootDir)) { + UUID uuid = UUID.randomUUID(); + storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + uuid.toString(); + } + messageStoreConfig.setStorePathRootDir(storePathRootDir); + return new DefaultMessageStore(messageStoreConfig, + new BrokerStatsManager("simpleTest", true), + new MyMessageArrivingListener(), + new BrokerConfig()); } @Test @@ -592,14 +608,15 @@ public void testRecover() throws Exception { //1.just reboot messageStore.shutdown(); - messageStore = buildMessageStore(); + String storeRootDir = ((DefaultMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir(); + messageStore = buildMessageStore(storeRootDir); boolean load = messageStore.load(); assertTrue(load); messageStore.start(); assertTrue(maxPhyOffset == messageStore.getMaxPhyOffset()); assertTrue(maxCqOffset == messageStore.getMaxOffsetInQueue(topic, 0)); - //2.damage commitlog and reboot normal + //2.damage commit-log and reboot normal for (int i = 0; i < 100; i++) { MessageExtBrokerInner messageExtBrokerInner = buildMessage(); messageExtBrokerInner.setTopic(topic); @@ -619,10 +636,10 @@ public void testRecover() throws Exception { messageStore.shutdown(); //damage last message - damageCommitlog(secondLastPhyOffset); + damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //reboot - messageStore = buildMessageStore(); + messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); @@ -648,14 +665,14 @@ public void testRecover() throws Exception { messageStore.shutdown(); //damage last message - damageCommitlog(secondLastPhyOffset); + damageCommitLog((DefaultMessageStore) messageStore, secondLastPhyOffset); //add abort file String fileName = StorePathConfigHelper.getAbortFile(((DefaultMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir()); File file = new File(fileName); UtilAll.ensureDirOK(file.getParent()); file.createNewFile(); - messageStore = buildMessageStore(); + messageStore = buildMessageStore(storeRootDir); load = messageStore.load(); assertTrue(load); messageStore.start(); @@ -687,24 +704,23 @@ private boolean fileExists(String path) { return false; } - private void damageCommitlog(long offset) throws Exception { - MessageStoreConfig messageStoreConfig = new MessageStoreConfig(); + private void damageCommitLog(DefaultMessageStore store, long offset) throws Exception { + assertThat(store).isNotNull(); + MessageStoreConfig messageStoreConfig = store.getMessageStoreConfig(); File file = new File(messageStoreConfig.getStorePathCommitLog() + File.separator + "00000000000000000000"); - - FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel(); - MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10); - - int bodyLen = mappedByteBuffer.getInt((int) offset + 84); - int topicLenIndex = (int) offset + 84 + bodyLen + 4; - mappedByteBuffer.position(topicLenIndex); - mappedByteBuffer.putInt(0); - mappedByteBuffer.putInt(0); - mappedByteBuffer.putInt(0); - mappedByteBuffer.putInt(0); - - mappedByteBuffer.force(); - fileChannel.force(true); - fileChannel.close(); + try (RandomAccessFile raf = new RandomAccessFile(file, "rw"); + FileChannel fileChannel = raf.getChannel()) { + MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10); + int bodyLen = mappedByteBuffer.getInt((int) offset + 84); + int topicLenIndex = (int) offset + 84 + bodyLen + 4; + mappedByteBuffer.position(topicLenIndex); + mappedByteBuffer.putInt(0); + mappedByteBuffer.putInt(0); + mappedByteBuffer.putInt(0); + mappedByteBuffer.putInt(0); + mappedByteBuffer.force(); + fileChannel.force(true); + } } @Test diff --git a/store/src/test/java/org/apache/rocketmq/store/HATest.java b/store/src/test/java/org/apache/rocketmq/store/HATest.java index ea7bf725fc0..231582f71f3 100644 --- a/store/src/test/java/org/apache/rocketmq/store/HATest.java +++ b/store/src/test/java/org/apache/rocketmq/store/HATest.java @@ -33,7 +33,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -41,10 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; -/** - * HATest - * - */ public class HATest { private final String StoreMessage = "Once, there was a chance for me!"; private int QUEUE_TOTAL = 100; @@ -58,7 +53,7 @@ public class HATest { private MessageStoreConfig masterMessageStoreConfig; private MessageStoreConfig slaveStoreConfig; private BrokerStatsManager brokerStatsManager = new BrokerStatsManager("simpleTest", true); - private String storePathRootParentDir = System.getProperty("user.home") + File.separator + + private String storePathRootParentDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID().toString().replace("-", ""); private String storePathRootDir = storePathRootParentDir + File.separator + "store"; @@ -70,6 +65,7 @@ public void init() throws Exception { masterMessageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER); masterMessageStoreConfig.setStorePathRootDir(storePathRootDir + File.separator + "master"); masterMessageStoreConfig.setStorePathCommitLog(storePathRootDir + File.separator + "master" + File.separator + "commitlog"); + masterMessageStoreConfig.setHaListenPort(0); masterMessageStoreConfig.setTotalReplicas(2); masterMessageStoreConfig.setInSyncReplicas(2); buildMessageStoreConfig(masterMessageStoreConfig); @@ -77,7 +73,7 @@ public void init() throws Exception { slaveStoreConfig.setBrokerRole(BrokerRole.SLAVE); slaveStoreConfig.setStorePathRootDir(storePathRootDir + File.separator + "slave"); slaveStoreConfig.setStorePathCommitLog(storePathRootDir + File.separator + "slave" + File.separator + "commitlog"); - slaveStoreConfig.setHaListenPort(10943); + slaveStoreConfig.setHaListenPort(0); slaveStoreConfig.setTotalReplicas(2); slaveStoreConfig.setInSyncReplicas(2); buildMessageStoreConfig(slaveStoreConfig); @@ -88,8 +84,10 @@ public void init() throws Exception { assertTrue(load); assertTrue(slaveLoad); messageStore.start(); + + slaveMessageStore.updateHaMasterAddress("127.0.0.1:" + masterMessageStoreConfig.getHaListenPort()); slaveMessageStore.start(); - slaveMessageStore.updateHaMasterAddress("127.0.0.1:10912"); + slaveMessageStore.updateHaMasterAddress("127.0.0.1:" + masterMessageStoreConfig.getHaListenPort()); Thread.sleep(6000L);//because the haClient will wait 5s after the first connectMaster failed,sleep 6s } @@ -119,7 +117,7 @@ public void testHandleHA() { for (long i = 0; i < totalMsgs; i++) { GetMessageResult result = slaveMessageStore.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null); assertThat(result).isNotNull(); - assertTrue(GetMessageStatus.FOUND.equals(result.getStatus())); + assertEquals(GetMessageStatus.FOUND, result.getStatus()); result.release(); } } @@ -138,7 +136,7 @@ public void testSemiSyncReplica() throws Exception { //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); - assertTrue(Arrays.equals(msg.getBody(), slaveMsg.getBody())); + assertArrayEquals(msg.getBody(), slaveMsg.getBody()); assertEquals(msg.getTopic(), slaveMsg.getTopic()); assertEquals(msg.getTags(), slaveMsg.getTags()); assertEquals(msg.getKeys(), slaveMsg.getKeys()); @@ -171,7 +169,7 @@ public void testSemiSyncReplicaWhenSlaveActingMaster() throws Exception { //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); - assertTrue(Arrays.equals(msg.getBody(), slaveMsg.getBody())); + assertArrayEquals(msg.getBody(), slaveMsg.getBody()); assertEquals(msg.getTopic(), slaveMsg.getTopic()); assertEquals(msg.getTags(), slaveMsg.getTags()); assertEquals(msg.getKeys(), slaveMsg.getKeys()); @@ -208,7 +206,7 @@ public void testSemiSyncReplicaWhenAdaptiveDegradation() throws Exception { //so direct read from commitLog by physical offset MessageExt slaveMsg = slaveMessageStore.lookMessageByOffset(result.getAppendMessageResult().getWroteOffset()); assertNotNull(slaveMsg); - assertTrue(Arrays.equals(msg.getBody(), slaveMsg.getBody())); + assertArrayEquals(msg.getBody(), slaveMsg.getBody()); assertEquals(msg.getTopic(), slaveMsg.getTopic()); assertEquals(msg.getTags(), slaveMsg.getTags()); assertEquals(msg.getKeys(), slaveMsg.getKeys()); diff --git a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java index a7e726c5751..92eae4be128 100644 --- a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java @@ -49,9 +49,9 @@ public void init() throws Exception { messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4); messageStoreConfig.setMaxHashSlotNum(100); messageStoreConfig.setMaxIndexNum(100 * 10); - messageStoreConfig.setStorePathRootDir(System.getProperty("user.home") + File.separator + "unitteststore1"); + messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1"); messageStoreConfig.setStorePathCommitLog( - System.getProperty("user.home") + File.separator + "unitteststore1" + File.separator + "commitlog"); + System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1" + File.separator + "commitlog"); messageStoreConfig.setEnableLmq(true); messageStoreConfig.setEnableMultiDispatch(true); @@ -64,7 +64,7 @@ public void init() throws Exception { @After public void destroy() { - UtilAll.deleteFile(new File(System.getProperty("user.home") + File.separator + "unitteststore1")); + UtilAll.deleteFile(new File(System.getProperty("java.io.tmpdir") + File.separator + "unitteststore1")); } @Test diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java b/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java index 9dc724cd3ef..3f2abf3ae97 100644 --- a/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java +++ b/store/src/test/java/org/apache/rocketmq/store/StoreTestBase.java @@ -150,7 +150,7 @@ protected MessageExtBrokerInner buildIPv6HostMessage() { } public static String createBaseDir() { - String baseDir = System.getProperty("user.home") + File.separator + "unitteststore" + File.separator + UUID.randomUUID(); + String baseDir = System.getProperty("java.io.tmpdir") + File.separator + "unitteststore" + File.separator + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { System.exit(1); diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java b/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java index c13995d135f..2a04392cbda 100644 --- a/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java +++ b/store/src/test/java/org/apache/rocketmq/store/timer/StoreTestUtils.java @@ -21,7 +21,7 @@ public class StoreTestUtils { public static String createBaseDir() { - String baseDir = System.getProperty("user.home") + File.separator + "unitteststore-" + UUID.randomUUID(); + String baseDir = System.getProperty("java.io.tmpdir") + File.separator + "unitteststore-" + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { System.exit(1); diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java index cc7ea41c7d8..926e7de00d7 100644 --- a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMessageStoreTest.java @@ -100,8 +100,6 @@ public void init() throws Exception { messageStore = new DefaultMessageStore(storeConfig, new BrokerStatsManager("TimerTest",false), new MyMessageArrivingListener(), new BrokerConfig()); boolean load = messageStore.load(); - List putMessageHookList = messageStore.getPutMessageHookList(); - assertTrue(load); messageStore.start(); } diff --git a/test/BUILD.bazel b/test/BUILD.bazel new file mode 100644 index 00000000000..5bff5ab36d5 --- /dev/null +++ b/test/BUILD.bazel @@ -0,0 +1,107 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "test", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//broker", + "//client", + "//common", + "//remoting", + "//logging", + "//srvutil", + "//tools", + "//namesrv", + "//controller", + "//container", + "//proxy", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:log4j_log4j", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:ch_qos_logback_logback_core", + "@maven//:com_google_truth_truth", + "@maven//:javax_annotation_javax_annotation_api", + "@maven//:org_awaitility_awaitility", + "@maven//:org_reflections_reflections", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:commons_cli_commons_cli", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":test", + "//broker", + "//client", + "//common", + "//remoting", + "//logging", + "//tools", + "//:test_deps", + "//store", + "//namesrv", + "//controller", + "//container", + "//proxy", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:com_google_truth_truth", + "@maven//:log4j_log4j", + "@maven//:io_grpc_grpc_testing", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:io_grpc_grpc_netty_shaded", + "@maven//:io_grpc_grpc_context", + "@maven//:io_grpc_grpc_stub", + "@maven//:io_grpc_grpc_api", + "@maven//:org_apache_rocketmq_rocketmq_proto", + "@maven//:org_slf4j_slf4j_api", + "@maven//:com_google_guava_guava", + ], + resources = [ + "src/test/resources/rmq-proxy-home/conf/broker.conf", + "src/test/resources/rmq-proxy-home/conf/logback_proxy.xml", + "src/test/resources/rmq-proxy-home/conf/rmq-proxy.json", + "src/test/resources/log4j.xml", + "src/test/resources/logback-test.xml", + ] + glob(["src/test/resources/schema/**/*.schema"]), +) + +GenTestRules( + name = "GeneratedTestRules", + default_test_size = "large", + test_files = glob(["src/test/java/**/*IT.java"]), + deps = [ + ":tests", + ], + exclude_tests = [ + "src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT", + ] +) diff --git a/test/src/main/java/org/apache/rocketmq/test/schema/SchemaTools.java b/test/src/main/java/org/apache/rocketmq/test/schema/SchemaTools.java index ab5b9749957..5184afc8838 100644 --- a/test/src/main/java/org/apache/rocketmq/test/schema/SchemaTools.java +++ b/test/src/main/java/org/apache/rocketmq/test/schema/SchemaTools.java @@ -177,7 +177,7 @@ public int compare(Class o1, Class o2) { } }); String key = String.format("Method %s(%s)", method.getName(), Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(","))); - String value = String.format("%s throws (%s)", + String value = String.format("%s throws (%s): %s", isPublicOrPrivate(method.getModifiers()), method.getReturnType().getName(), Arrays.stream(exceptionTypes).map(Class::getName).collect(Collectors.joining(","))); diff --git a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java index 41cacb684c3..5b62a6377d8 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java @@ -265,7 +265,7 @@ public static int[] getRandomArray(int min, int max, int n) { Random rd = new Random(); int index = 0; for (int i = 0; i < result.length; i++) { - index = Math.abs(rd.nextInt() % len--); + index = rd.nextInt(len--); result[i] = source[index]; source[index] = source[len]; } diff --git a/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java b/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java index 9b09d16320f..08176cc94c8 100644 --- a/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java +++ b/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java @@ -56,15 +56,12 @@ public class IntegrationTestBase { public static volatile int COMMIT_LOG_SIZE = 1024 * 1024 * 100; protected static final int INDEX_NUM = 1000; - private static final AtomicInteger port = new AtomicInteger(40000); - - public static synchronized int nextPort() { - return port.addAndGet(random.nextInt(10) + 10); - } protected static Random random = new Random(); static { + System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); + Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { @@ -99,7 +96,7 @@ public void run() { } public static String createBaseDir() { - String baseDir = System.getProperty("user.home") + SEP + "unitteststore-" + UUID.randomUUID(); + String baseDir = System.getProperty("java.io.tmpdir") + SEP + "unitteststore-" + UUID.randomUUID(); final File file = new File(baseDir); if (file.exists()) { logger.info(String.format("[%s] has already existed, please back up and remove it for integration tests", baseDir)); @@ -116,7 +113,7 @@ public static NamesrvController createAndStartNamesrv() { namesrvConfig.setKvConfigPath(baseDir + SEP + "namesrv" + SEP + "kvConfig.json"); namesrvConfig.setConfigStorePath(baseDir + SEP + "namesrv" + SEP + "namesrv.properties"); - nameServerNettyServerConfig.setListenPort(nextPort()); + nameServerNettyServerConfig.setListenPort(0); NamesrvController namesrvController = new NamesrvController(namesrvConfig, nameServerNettyServerConfig); try { Truth.assertThat(namesrvController.initialize()).isTrue(); @@ -154,8 +151,8 @@ public static BrokerController createAndStartBroker(String nsAddr) { public static BrokerController createAndStartBroker(MessageStoreConfig storeConfig, BrokerConfig brokerConfig) { NettyServerConfig nettyServerConfig = new NettyServerConfig(); NettyClientConfig nettyClientConfig = new NettyClientConfig(); - nettyServerConfig.setListenPort(nextPort()); - storeConfig.setHaListenPort(nextPort()); + nettyServerConfig.setListenPort(0); + storeConfig.setHaListenPort(0); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, nettyClientConfig, storeConfig); try { Truth.assertThat(brokerController.initialize()).isTrue(); diff --git a/test/src/test/java/org/apache/rocketmq/test/base/dledger/DLedgerProduceAndConsumeIT.java b/test/src/test/java/org/apache/rocketmq/test/base/dledger/DLedgerProduceAndConsumeIT.java index 7b9b9e5f335..ab8ec964a69 100644 --- a/test/src/test/java/org/apache/rocketmq/test/base/dledger/DLedgerProduceAndConsumeIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/base/dledger/DLedgerProduceAndConsumeIT.java @@ -38,7 +38,6 @@ import org.junit.Assert; import org.junit.Test; -import static org.apache.rocketmq.test.base.IntegrationTestBase.nextPort; import static sun.util.locale.BaseLocale.SEP; public class DLedgerProduceAndConsumeIT { @@ -57,7 +56,7 @@ public MessageStoreConfig buildStoreConfig(String brokerName, String peers, Stri String baseDir = IntegrationTestBase.createBaseDir(); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + SEP + "commitlog"); - storeConfig.setHaListenPort(nextPort()); + storeConfig.setHaListenPort(0); storeConfig.setMappedFileSizeCommitLog(10 * 1024 * 1024); storeConfig.setEnableDLegerCommitLog(true); storeConfig.setdLegerGroup(brokerName); @@ -71,7 +70,8 @@ public void testProduceAndConsume() throws Exception { String cluster = UUID.randomUUID().toString(); String brokerName = UUID.randomUUID().toString(); String selfId = "n0"; - String peers = String.format("n0-localhost:%d", nextPort()); + // TODO: We need to acquire the actual listening port after the peer has started. + String peers = String.format("n0-localhost:%d", 0); BrokerConfig brokerConfig = buildBrokerConfig(cluster, brokerName); MessageStoreConfig storeConfig = buildStoreConfig(brokerName, peers, selfId); BrokerController brokerController = IntegrationTestBase.createAndStartBroker(storeConfig, brokerConfig); diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadCastIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadcast.java similarity index 87% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadCastIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadcast.java index c9ce7665cf0..4eff93951a9 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadCastIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/BaseBroadcast.java @@ -23,21 +23,21 @@ import org.apache.rocketmq.test.factory.ConsumerFactory; import org.apache.rocketmq.test.listener.AbstractListener; -public class BaseBroadCastIT extends BaseConf { - private static Logger logger = Logger.getLogger(BaseBroadCastIT.class); +public class BaseBroadcast extends BaseConf { + private static Logger logger = Logger.getLogger(BaseBroadcast.class); public static RMQBroadCastConsumer getBroadCastConsumer(String nsAddr, String topic, String subExpression, - AbstractListener listner) { + AbstractListener listener) { String consumerGroup = initConsumerGroup(); - return getBroadCastConsumer(nsAddr, consumerGroup, topic, subExpression, listner); + return getBroadCastConsumer(nsAddr, consumerGroup, topic, subExpression, listener); } public static RMQBroadCastConsumer getBroadCastConsumer(String nsAddr, String consumerGroup, String topic, String subExpression, - AbstractListener listner) { + AbstractListener listener) { RMQBroadCastConsumer consumer = ConsumerFactory.getRMQBroadCastConsumer(nsAddr, - consumerGroup, topic, subExpression, listner); + consumerGroup, topic, subExpression, listener); consumer.setDebug(); diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgNotRecvIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgNotReceiveIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgNotRecvIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgNotReceiveIT.java index f0e42ce63c8..ca5774e23a1 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgNotRecvIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgNotReceiveIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -30,7 +30,7 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastNormalMsgNotRecvIT extends BaseBroadCastIT { +public class BroadcastNormalMsgNotReceiveIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvCrashIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvCrashIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvCrashIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvCrashIT.java index cf61610aef1..c3839327366 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvCrashIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvCrashIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,7 +31,7 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastNormalMsgRecvCrashIT extends BaseBroadCastIT { +public class BroadcastNormalMsgRecvCrashIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvFailIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvFailIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvFailIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvFailIT.java index b4a963a10c7..f040d42a10c 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvFailIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvFailIT.java @@ -19,7 +19,7 @@ import org.apache.log4j.Logger; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -32,7 +32,7 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastNormalMsgRecvFailIT extends BaseBroadCastIT { +public class BroadcastNormalMsgRecvFailIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvStartLaterIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvStartLaterIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvStartLaterIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvStartLaterIT.java index 6966faa6720..6ddfc6709ef 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgRecvStartLaterIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgRecvStartLaterIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,7 +31,7 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastNormalMsgRecvStartLaterIT extends BaseBroadCastIT { +public class BroadcastNormalMsgRecvStartLaterIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgTwoDiffGroupRecvIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgTwoDiffGroupRecvIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgTwoDiffGroupRecvIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgTwoDiffGroupRecvIT.java index 5497d168351..13c47857ebd 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadCastNormalMsgTwoDiffGroupRecvIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/BroadcastNormalMsgTwoDiffGroupRecvIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,7 +31,7 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastNormalMsgTwoDiffGroupRecvIT extends BaseBroadCastIT { +public class BroadcastNormalMsgTwoDiffGroupRecvIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/NormalMsgTwoSameGroupConsumerIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/NormalMsgTwoSameGroupConsumerIT.java index 7cf0785e8bb..8754c265988 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/NormalMsgTwoSameGroupConsumerIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/normal/NormalMsgTwoSameGroupConsumerIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.normal; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,7 +31,7 @@ import static com.google.common.truth.Truth.assertThat; -public class NormalMsgTwoSameGroupConsumerIT extends BaseBroadCastIT { +public class NormalMsgTwoSameGroupConsumerIT extends BaseBroadcast { private static Logger logger = Logger .getLogger(NormalMsgTwoSameGroupConsumerIT.class); private RMQNormalProducer producer = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadCastIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadcastIT.java similarity index 97% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadCastIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadcastIT.java index 1d7ea2033ac..ef58cb4b0cb 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadCastIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/order/OrderMsgBroadcastIT.java @@ -20,7 +20,7 @@ import java.util.List; import org.apache.log4j.Logger; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.order.RMQOrderListener; @@ -38,8 +38,8 @@ * Currently, dose not support the ordered broadcast message */ @Ignore -public class OrderMsgBroadCastIT extends BaseBroadCastIT { - private static Logger logger = Logger.getLogger(OrderMsgBroadCastIT.class); +public class OrderMsgBroadcastIT extends BaseBroadcast { + private static Logger logger = Logger.getLogger(OrderMsgBroadcastIT.class); private RMQNormalProducer producer = null; private String topic = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerFilterIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerFilterIT.java similarity index 95% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerFilterIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerFilterIT.java index a83270b94a6..eddb5890629 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerFilterIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerFilterIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,8 +31,8 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastTwoConsumerFilterIT extends BaseBroadCastIT { - private static Logger logger = Logger.getLogger(BroadCastTwoConsumerSubTagIT.class); +public class BroadcastTwoConsumerFilterIT extends BaseBroadcast { + private static Logger logger = Logger.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubDiffTagIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubDiffTagIT.java similarity index 95% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubDiffTagIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubDiffTagIT.java index 9ffe05c3c3d..0f69d4d8b62 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubDiffTagIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubDiffTagIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,8 +31,8 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastTwoConsumerSubDiffTagIT extends BaseBroadCastIT { - private static Logger logger = Logger.getLogger(BroadCastTwoConsumerSubTagIT.class); +public class BroadcastTwoConsumerSubDiffTagIT extends BaseBroadcast { + private static Logger logger = Logger.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubTagIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubTagIT.java similarity index 95% rename from test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubTagIT.java rename to test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubTagIT.java index 1c86a26b6ea..6dfb0526901 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadCastTwoConsumerSubTagIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/broadcast/tag/BroadcastTwoConsumerSubTagIT.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.test.client.consumer.broadcast.tag; import org.apache.log4j.Logger; -import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadCastIT; +import org.apache.rocketmq.test.client.consumer.broadcast.BaseBroadcast; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; @@ -31,8 +31,8 @@ import static com.google.common.truth.Truth.assertThat; -public class BroadCastTwoConsumerSubTagIT extends BaseBroadCastIT { - private static Logger logger = Logger.getLogger(BroadCastTwoConsumerSubTagIT.class); +public class BroadcastTwoConsumerSubTagIT extends BaseBroadcast { + private static Logger logger = Logger.getLogger(BroadcastTwoConsumerSubTagIT.class); private RMQNormalProducer producer = null; private String topic = null; diff --git a/test/src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT.java b/test/src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT.java index 532d6d4ccdf..df8b3783893 100644 --- a/test/src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/grpc/v2/GrpcBaseIT.java @@ -140,7 +140,11 @@ public void setUp() throws Exception { if (mockProxyHomeURL != null) { mockProxyHome = mockProxyHomeURL.toURI().getPath(); } - System.setProperty(RMQ_PROXY_HOME, mockProxyHome); + + if (null != mockProxyHome) { + System.setProperty(RMQ_PROXY_HOME, mockProxyHome); + } + ConfigurationManager.initEnv(); ConfigurationManager.intConfig(); ConfigurationManager.getProxyConfig().setGrpcServerPort(port); diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel new file mode 100644 index 00000000000..51bfdcac602 --- /dev/null +++ b/tools/BUILD.bazel @@ -0,0 +1,68 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +load("//bazel:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "tools", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//acl", + "//remoting", + "//logging", + "//client", + "//common", + "//srvutil", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:commons_validator_commons_validator", + "@maven//:com_github_luben_zstd_jni", + "@maven//:org_lz4_lz4_java", + "@maven//:com_alibaba_fastjson", + "@maven//:io_netty_netty_all", + "@maven//:commons_cli_commons_cli", + "@maven//:org_slf4j_slf4j_api", + "@maven//:ch_qos_logback_logback_classic", + "@maven//:ch_qos_logback_logback_core", + "@maven//:commons_collections_commons_collections", + ], +) + +java_library( + name = "tests", + srcs = glob(["src/test/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + ":tools", + "//client", + "//common", + "//srvutil", + "//remoting", + "//:test_deps", + "@maven//:org_apache_commons_commons_lang3", + "@maven//:io_netty_netty_all", + "@maven//:commons_cli_commons_cli", + ], + resources = glob(["src/test/resources/certs/*.pem"]) + glob(["src/test/resources/certs/*.key"]) +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["src/test/java/**/*Test.java"]), + deps = [ + ":tests", + ], +) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java index 2673c6747a9..52aded62dde 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java @@ -18,7 +18,6 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.core.joran.spi.JoranException; import java.util.ArrayList; import java.util.List; import java.nio.file.Files; @@ -268,7 +267,7 @@ public static void initCommand() { initCommand(new ReElectMasterSubCommand()); } - private static void initLogback() throws JoranException { + private static void initLogback() throws Exception { LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(lc); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java index 230e4c47d72..7aaf802c837 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java @@ -80,7 +80,7 @@ public void execute(CommandLine commandLine, Options options, defaultMQAdminExt.start(); printClusterBaseInfo(defaultMQAdminExt, addr); - System.out.printf("get broker's plain access config version success.%n", addr); + System.out.printf("get broker's plain access config version success. Address:%s %n", addr); return; } else if (commandLine.hasOption('c')) { diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java index bc867246fa6..87108aafa9c 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java @@ -58,6 +58,10 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + try { defaultMQAdminExt.start(); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java index 86464c29954..523f41191b2 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java @@ -99,6 +99,10 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt(rpcHook); defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + try { defaultMQAdminExt.start(); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java index 80cdb26da9b..fbd5f732f87 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java @@ -77,6 +77,10 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + try { defaultMQAdminExt.start(); String group = commandLine.getOptionValue('g').trim(); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java index 4696b4f46cf..ba48c68af89 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java @@ -60,6 +60,11 @@ public void execute(CommandLine commandLine, Options options, DefaultMQAdminExt adminExt = new DefaultMQAdminExt(rpcHook); adminExt.setInstanceName(Long.toString(System.currentTimeMillis())); String groupName = commandLine.getOptionValue('g').trim(); + + if (commandLine.hasOption('n')) { + adminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + try { adminExt.start(); List consumerConfigInfoList = new ArrayList<>(); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java index 5496a378ff7..c3d30f45914 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java @@ -76,6 +76,9 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t if (commandLine.hasOption('t')) { traceTopic = commandLine.getOptionValue('t').trim(); } + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } this.queryTraceByMsgId(defaultMQAdminExt, traceTopic, msgId); } catch (Exception e) { throw new SubCommandException(this.getClass().getSimpleName() + "command failed", e); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommand.java index 56b4e72c0e1..7d3a809becb 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommand.java @@ -63,6 +63,10 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t // key name String value = commandLine.getOptionValue('v').trim(); + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + defaultMQAdminExt.start(); defaultMQAdminExt.createAndUpdateKvConfig(namespace, key, value); System.out.printf("create or update kv config to namespace success.%n"); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommand.java index be5ec61e373..b731af5cb63 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommand.java @@ -67,6 +67,11 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t if (commandLine.hasOption("i")) { originClientId = commandLine.getOptionValue("i").trim(); } + + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + defaultMQAdminExt.start(); Map> consumerStatusTable = diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommand.java index 6e4e5705e50..eeb95c6e840 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommand.java @@ -114,6 +114,10 @@ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) t queueId = Integer.valueOf(commandLine.getOptionValue('q')); } + if (commandLine.hasOption('n')) { + defaultMQAdminExt.setNamesrvAddr(commandLine.getOptionValue('n').trim()); + } + defaultMQAdminExt.start(); if (brokerAddr != null && queueId > -1) { diff --git a/tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtTest.java b/tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtTest.java index 9443c8eb04b..c604f0c3a31 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtTest.java @@ -481,6 +481,6 @@ public void testSearchOffset() throws Exception { @Test public void testExamineTopicConfig() throws MQBrokerException, RemotingException, InterruptedException { TopicConfig topicConfig = defaultMQAdminExt.examineTopicConfig("127.0.0.1:10911", "topic_test_examine_topicConfig"); - assertThat(topicConfig.getTopicName().equals("topic_test_examine_topicConfig")); + assertThat(topicConfig.getTopicName().equals("topic_test_examine_topicConfig")).isTrue(); } } \ No newline at end of file diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommandTest.java index b893a765cf2..91145e72532 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/broker/CleanUnusedTopicCommandTest.java @@ -26,11 +26,9 @@ public class CleanUnusedTopicCommandTest extends ServerResponseMocker { - private static final int PORT = 45678; - @Override protected int getPort() { - return PORT; + return 0; } @Override @@ -42,7 +40,7 @@ protected byte[] getBody() { public void testExecute() throws SubCommandException { CleanUnusedTopicCommand cmd = new CleanUnusedTopicCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-b 127.0.0.1:" + PORT, "-c default-cluster"}; + String[] subargs = new String[] {"-b 127.0.0.1:" + listenPort(), "-c default-cluster"}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommandTest.java index 4cab7ccb7c7..c44ea3acbe8 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommandTest.java @@ -35,10 +35,6 @@ public class ProducerConnectionSubCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -46,7 +42,7 @@ public class ProducerConnectionSubCommandTest { @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -59,7 +55,7 @@ public void after() { public void testExecute() throws SubCommandException { ProducerConnectionSubCommand cmd = new ProducerConnectionSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g default-producer-group", "-t unit-test"}; + String[] subargs = new String[] {"-g default-producer-group", "-t unit-test", String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -73,6 +69,6 @@ private ServerResponseMocker startOneBroker() { producerConnection.setConnectionSet(connectionSet); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, producerConnection.encode()); + return ServerResponseMocker.startServer(0, producerConnection.encode()); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommandTest.java index d6b83be07af..5db2af460cf 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommandTest.java @@ -35,10 +35,6 @@ public class ConsumerProgressSubCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -46,7 +42,7 @@ public class ConsumerProgressSubCommandTest { @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -60,7 +56,8 @@ public void after() { public void testExecute() throws SubCommandException { ConsumerProgressSubCommand cmd = new ConsumerProgressSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g default-group"}; + String[] subargs = new String[] {"-g default-group", + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -82,6 +79,6 @@ private ServerResponseMocker startOneBroker() { offsetTable.put(messageQueue, offsetWrapper); consumeStats.setOffsetTable(offsetTable); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, consumeStats.encode()); + return ServerResponseMocker.startServer(0, consumeStats.encode()); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommandTest.java index 82baaeb981c..e9f19c24748 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommandTest.java @@ -35,10 +35,6 @@ public class ConsumerStatusSubCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -46,7 +42,7 @@ public class ConsumerStatusSubCommandTest { @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -59,7 +55,8 @@ public void after() { public void testExecute() throws SubCommandException { ConsumerStatusSubCommand cmd = new ConsumerStatusSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g default-group", "-i cid_one"}; + String[] subargs = new String[] {"-g default-group", "-i cid_one", + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -72,6 +69,6 @@ private ServerResponseMocker startOneBroker() { connectionSet.add(connection); consumerConnection.setConnectionSet(connectionSet); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, consumerConnection.encode()); + return ServerResponseMocker.startServer(0, consumerConnection.encode()); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommandTest.java index c21df3d1ac6..3f123883a6b 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommandTest.java @@ -26,6 +26,7 @@ import org.apache.rocketmq.common.protocol.route.BrokerData; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.tools.command.SubCommandException; +import org.apache.rocketmq.tools.command.server.NameServerMocker; import org.apache.rocketmq.tools.command.server.ServerResponseMocker; import org.junit.After; import org.junit.Before; @@ -39,10 +40,6 @@ public class GetConsumerConfigSubCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -63,21 +60,22 @@ public void after() { public void testExecute() throws SubCommandException { GetConsumerConfigSubCommand cmd = new GetConsumerConfigSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g group_test"}; + String[] subargs = new String[] {"-g group_test", String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = - ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); + ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, + cmd.buildCommandlineOptions(options), + new PosixParser()); cmd.execute(commandLine, options, null); } private ServerResponseMocker startNameServer() { - System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "127.0.0.1:" + NAME_SERVER_PORT); ClusterInfo clusterInfo = new ClusterInfo(); HashMap brokerAddressTable = new HashMap<>(); BrokerData brokerData = new BrokerData(); brokerData.setBrokerName("mockBrokerName"); HashMap brokerAddress = new HashMap<>(); - brokerAddress.put(1L, "127.0.0.1:" + BROKER_PORT); + brokerAddress.put(1L, "127.0.0.1:" + brokerMocker.listenPort()); brokerData.setBrokerAddrs(brokerAddress); brokerData.setCluster("mockCluster"); brokerAddressTable.put("mockBrokerName", brokerData); @@ -90,7 +88,7 @@ private ServerResponseMocker startNameServer() { clusterInfo.setClusterAddrTable(clusterAddressTable); // start name server - return ServerResponseMocker.startServer(NAME_SERVER_PORT, clusterInfo.encode()); + return ServerResponseMocker.startServer(0, clusterInfo.encode()); } private ServerResponseMocker startOneBroker() { @@ -100,6 +98,6 @@ private ServerResponseMocker startOneBroker() { connectionSet.add(connection); consumerConnection.setConnectionSet(connectionSet); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, consumerConnection.encode()); + return ServerResponseMocker.startServer(0, consumerConnection.encode()); } } \ No newline at end of file diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommandTest.java index f22acdead7b..d9f88cd0156 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommandTest.java @@ -41,10 +41,6 @@ public class QueryMsgTraceByIdSubCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -67,19 +63,18 @@ public void after() { public void testExecute() throws SubCommandException { QueryMsgTraceByIdSubCommand cmd = new QueryMsgTraceByIdSubCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-i " + MSG_ID}; + String[] subargs = new String[] {String.format("-i %s", MSG_ID), + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); } private ServerResponseMocker startNameServer() { - int nameServerPort = NAME_SERVER_PORT; - System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, "127.0.0.1:" + nameServerPort); TopicRouteData topicRouteData = new TopicRouteData(); List dataList = new ArrayList<>(); HashMap brokerAddress = new HashMap<>(); - brokerAddress.put(1L, "127.0.0.1:" + BROKER_PORT); + brokerAddress.put(1L, "127.0.0.1:" + brokerMocker.listenPort()); BrokerData brokerData = new BrokerData("mockCluster", "mockBrokerName", brokerAddress); brokerData.setBrokerName("mockBrokerName"); dataList.add(brokerData); @@ -95,7 +90,7 @@ private ServerResponseMocker startNameServer() { queueDatas.add(queueData); topicRouteData.setQueueDatas(queueDatas); - return ServerResponseMocker.startServer(nameServerPort, topicRouteData.encode()); + return ServerResponseMocker.startServer(0, topicRouteData.encode()); } private ServerResponseMocker startOneBroker() { @@ -113,7 +108,7 @@ private ServerResponseMocker startOneBroker() { extMap.put("indexLastUpdateTimestamp", String.valueOf(System.currentTimeMillis())); extMap.put("indexLastUpdatePhyoffset", String.valueOf(System.currentTimeMillis())); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, body, extMap); + return ServerResponseMocker.startServer(0, body, extMap); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommandTest.java index c7480d1f9cf..9dac57dea03 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/namesrv/UpdateKvConfigCommandTest.java @@ -28,10 +28,6 @@ import org.junit.Test; public class UpdateKvConfigCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -39,7 +35,7 @@ public class UpdateKvConfigCommandTest { @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -52,7 +48,8 @@ public void after() { public void testExecute() throws SubCommandException { UpdateKvConfigCommand cmd = new UpdateKvConfigCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[]{"-s namespace", "-k topicname", "-v unit_test"}; + String[] subargs = new String[]{"-s namespace", "-k topicname", "-v unit_test", + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName() + cmd.commandDesc(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -60,6 +57,6 @@ public void testExecute() throws SubCommandException { private ServerResponseMocker startOneBroker() { // start broker - return ServerResponseMocker.startServer(BROKER_PORT, null); + return ServerResponseMocker.startServer(0, null); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommandTest.java index aece90feec6..21bb1820dc1 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/offset/GetConsumerStatusCommandTest.java @@ -26,22 +26,24 @@ import org.apache.rocketmq.tools.command.server.ServerResponseMocker; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; public class GetConsumerStatusCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; + @BeforeClass + public static void setUpEnv() { + System.setProperty("rocketmq.client.logRoot", System.getProperty("java.io.tmpdir")); + } + @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -54,7 +56,8 @@ public void after() { public void testExecute() throws SubCommandException { GetConsumerStatusCommand cmd = new GetConsumerStatusCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g default-group", "-t unit-test", "-i clientid"}; + String[] subargs = new String[] {"-g default-group", "-t unit-test", "-i clientid", + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -63,6 +66,6 @@ public void testExecute() throws SubCommandException { private ServerResponseMocker startOneBroker() { GetConsumerStatusBody getConsumerStatusBody = new GetConsumerStatusBody(); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, getConsumerStatusBody.encode()); + return ServerResponseMocker.startServer(0, getConsumerStatusBody.encode()); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommandTest.java index 03e8943ebf1..8ea29f0fbe1 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommandTest.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/offset/ResetOffsetByTimeCommandTest.java @@ -30,10 +30,6 @@ public class ResetOffsetByTimeCommandTest { - private static final int NAME_SERVER_PORT = 45677; - - private static final int BROKER_PORT = 45676; - private ServerResponseMocker brokerMocker; private ServerResponseMocker nameServerMocker; @@ -41,7 +37,7 @@ public class ResetOffsetByTimeCommandTest { @Before public void before() { brokerMocker = startOneBroker(); - nameServerMocker = NameServerMocker.startByDefaultConf(NAME_SERVER_PORT, BROKER_PORT); + nameServerMocker = NameServerMocker.startByDefaultConf(0, brokerMocker.listenPort()); } @After @@ -54,7 +50,8 @@ public void after() { public void testExecute() throws SubCommandException { ResetOffsetByTimeCommand cmd = new ResetOffsetByTimeCommand(); Options options = ServerUtil.buildCommandlineOptions(new Options()); - String[] subargs = new String[] {"-g default-group", "-t unit-test", "-s 1412131213231", "-f false"}; + String[] subargs = new String[] {"-g default-group", "-t unit-test", "-s 1412131213231", "-f false", + String.format("-n localhost:%d", nameServerMocker.listenPort())}; final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs, cmd.buildCommandlineOptions(options), new PosixParser()); cmd.execute(commandLine, options, null); @@ -63,6 +60,6 @@ public void testExecute() throws SubCommandException { private ServerResponseMocker startOneBroker() { ResetOffsetBody resetOffsetBody = new ResetOffsetBody(); // start broker - return ServerResponseMocker.startServer(BROKER_PORT, resetOffsetBody.encode()); + return ServerResponseMocker.startServer(0, resetOffsetBody.encode()); } } diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/server/ServerResponseMocker.java b/tools/src/test/java/org/apache/rocketmq/tools/command/server/ServerResponseMocker.java index 20ddf62ed4b..b8dcaef2d52 100644 --- a/tools/src/test/java/org/apache/rocketmq/tools/command/server/ServerResponseMocker.java +++ b/tools/src/test/java/org/apache/rocketmq/tools/command/server/ServerResponseMocker.java @@ -45,6 +45,8 @@ */ public abstract class ServerResponseMocker { + private int listenPort; + private final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(); @Before @@ -67,6 +69,10 @@ public void shutdown() { protected abstract int getPort(); + public int listenPort() { + return listenPort; + } + protected abstract byte[] getBody(); public void start() { @@ -83,7 +89,6 @@ public void start(HashMap extMap) { .childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.SO_SNDBUF, 65535) .childOption(ChannelOption.SO_RCVBUF, 65535) - .localAddress(new InetSocketAddress(getPort())) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { @@ -98,8 +103,9 @@ public void initChannel(SocketChannel ch) throws Exception { } }); try { - ChannelFuture sync = serverBootstrap.bind().sync(); + ChannelFuture sync = serverBootstrap.bind(getPort()).sync(); InetSocketAddress addr = (InetSocketAddress) sync.channel().localAddress(); + this.listenPort = addr.getPort(); } catch (InterruptedException e1) { throw new RuntimeException("this.serverBootstrap.bind().sync() InterruptedException", e1); }