Merge remote-tracking branch 'apache/develop' into 5.0.0-alpha

# Conflicts:
#	acl/pom.xml
#	broker/pom.xml
#	client/pom.xml
#	common/pom.xml
#	common/src/main/java/org/apache/rocketmq/common/MQVersion.java
#	distribution/pom.xml
#	example/pom.xml
#	filter/pom.xml
#	logging/pom.xml
#	namesrv/pom.xml
#	openmessaging/pom.xml
#	pom.xml
#	remoting/pom.xml
#	srvutil/pom.xml
#	store/pom.xml
#	store/src/test/java/org/apache/rocketmq/store/dledger/DLedgerCommitlogTest.java
#	test/pom.xml
#	tools/pom.xml
This commit is contained in:
RongtongJin
2022-03-12 21:15:45 +08:00
36 changed files with 986 additions and 114 deletions
+17 -6
View File
@@ -4,11 +4,22 @@ about: Describe this issue template's purpose here.
---
The issue tracker is **ONLY** used for bug report(feature request need to follow [RIP process](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal)). Keep in mind, please check whether there is an existing same report before your raise a new one.
The issue tracker is used for bug reporting purposes **ONLY** whereas feature request needs to follow the [RIP process](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal). To avoid unnecessary duplication, please check whether there is a previous issue before filing a new one.
Alternately (especially if your communication is not a bug report), you can send mail to our [mailing lists](http://rocketmq.apache.org/about/contact/). We welcome any friendly suggestions, bug fixes, collaboration and other improvements.
It is recommended to start a discussion thread in the [mailing lists](http://rocketmq.apache.org/about/contact/) in cases of discussing your deployment plan, API clarification, and other non-bug-reporting issues.
We welcome any friendly suggestions, bug fixes, collaboration, and other improvements.
Please ensure that your bug report is clear and that it is complete. Otherwise, we may be unable to understand it or to reproduce it, either of which would prevent us from fixing the bug. We strongly recommend the report(bug report or feature request) could include some hints as the following:
Please ensure that your bug report is clear and self-contained. Otherwise, it would take additional rounds of communication, thus more time, to understand the problem itself.
Generally, fixing an issue goes through the following steps:
1. Understand the issue reported;
1. Reproduce the unexpected behavior locally;
1. Perform root cause analysis to identify the underlying problem;
1. Create test cases to cover the identified problem;
1. Work out a solution to rectify the behavior and make the newly created test cases pass;
1. Make a pull request and go through peer review;
As a result, it would be very helpful yet challenging if you could provide an isolated project reproducing your reported issue. Anyway, please ensure your issue report is informative enough for the community to pick up. At a minimum, include the following hints:
**BUG REPORT**
@@ -16,13 +27,13 @@ Please ensure that your bug report is clear and that it is complete. Otherwise,
- What did you do (The steps to reproduce)?
- What did you expect to see?
- What is expected to see?
- What did you see instead?
2. Please tell us about your environment:
3. Other information (e.g. detailed explanation, logs, related issues, suggestions how to fix, etc):
3. Other information (e.g. detailed explanation, logs, related issues, suggestions on how to fix, etc):
**FEATURE REQUEST**
@@ -32,7 +43,7 @@ Please ensure that your bug report is clear and that it is complete. Otherwise,
2. Indicate the importance of this issue to you (blocker, must-have, should-have, nice-to-have). Are you currently using any workarounds to address this issue?
4. If there are some sub-tasks using -[] for each subtask and create a corresponding issue to map to the sub task:
4. If there are some sub-tasks involved, use -[] for each sub-task and create a corresponding issue to map to the sub-task:
- [sub-task1-issue-number](example_sub_issue1_link_here): sub-task1 description here,
- [sub-task2-issue-number](example_sub_issue2_link_here): sub-task2 description here,
+2
View File
@@ -45,6 +45,8 @@ It offers a variety of features:
* [RocketMQ Exporter](https://github.com/apache/rocketmq-exporter)
* [RocketMQ Operator](https://github.com/apache/rocketmq-operator)
* [RocketMQ Docker](https://github.com/apache/rocketmq-docker)
* [RocketMQ Dashboard](https://github.com/apache/rocketmq-dashboard)
* [RocketMQ Connect](https://github.com/apache/rocketmq-connect)
* [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals)
----------
@@ -163,15 +163,6 @@ public class PlainAccessValidator implements AccessValidator {
return aclPlugEngine.getAllAclConfig();
}
public Map<String, Object> createAclAccessConfigMap(Map<String, Object> existedAccountMap,
PlainAccessConfig plainAccessConfig) {
return aclPlugEngine.createAclAccessConfigMap(existedAccountMap, plainAccessConfig);
}
public Map<String, Object> updateAclConfigFileVersion(Map<String, Object> updateAclConfigMap) {
return aclPlugEngine.updateAclConfigFileVersion(updateAclConfigMap);
}
@Override
public Map<String, DataVersion> getAllAclConfigVersion() {
return aclPlugEngine.getDataVersionMap();
@@ -21,11 +21,16 @@ import com.alibaba.fastjson.JSONObject;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -113,16 +118,20 @@ public class PlainPermissionManager {
Map<String, List<RemoteAddressStrategy>> globalWhiteRemoteAddressStrategyMap = new HashMap<>();
Map<String, DataVersion> dataVersionMap = new HashMap<>();
assureAclConfigFilesExist();
fileList = getAllAclFiles(defaultAclDir);
if (new File(defaultAclFile).exists() && !fileList.contains(defaultAclFile)) {
fileList.add(defaultAclFile);
}
for (int i = 0; i < fileList.size(); i++) {
JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileList.get(i),
final String currentFile = fileList.get(i);
JSONObject plainAclConfData = AclUtils.getYamlDataObject(currentFile,
JSONObject.class);
if (plainAclConfData == null || plainAclConfData.isEmpty()) {
throw new AclException(String.format("%s file is not data", fileList.get(i)));
log.warn("No data in file {}", currentFile);
continue;
}
log.info("Broker plain acl conf data is : ", plainAclConfData.toString());
@@ -135,7 +144,7 @@ public class PlainPermissionManager {
}
}
if (globalWhiteRemoteAddressStrategyList.size() > 0) {
globalWhiteRemoteAddressStrategyMap.put(fileList.get(i), globalWhiteRemoteAddressStrategyList);
globalWhiteRemoteAddressStrategyMap.put(currentFile, globalWhiteRemoteAddressStrategyList);
globalWhiteRemoteAddressStrategy.addAll(globalWhiteRemoteAddressStrategyList);
}
@@ -148,14 +157,14 @@ public class PlainPermissionManager {
//AccessKey can not be defined in multiple ACL files
if (accessKeyTable.get(plainAccessResource.getAccessKey()) == null) {
plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource);
accessKeyTable.put(plainAccessResource.getAccessKey(), fileList.get(i));
accessKeyTable.put(plainAccessResource.getAccessKey(), currentFile);
} else {
log.warn("The accesssKey {} is repeated in multiple ACL files", plainAccessResource.getAccessKey());
}
}
}
if (plainAccessResourceMap.size() > 0) {
aclPlainAccessResourceMap.put(fileList.get(i), plainAccessResourceMap);
aclPlainAccessResourceMap.put(currentFile, plainAccessResourceMap);
}
JSONArray tempDataVersion = plainAclConfData.getJSONArray(AclConstants.CONFIG_DATA_VERSION);
@@ -165,7 +174,7 @@ public class PlainPermissionManager {
DataVersion firstElement = dataVersions.get(0);
dataVersion.assignNewOne(firstElement);
}
dataVersionMap.put(fileList.get(i), dataVersion);
dataVersionMap.put(currentFile, dataVersion);
}
if (dataVersionMap.containsKey(defaultAclFile)) {
@@ -178,6 +187,23 @@ public class PlainPermissionManager {
this.accessKeyTable = accessKeyTable;
}
/**
* Currently GlobalWhiteAddress is defined in {@link #defaultAclFile}, so make sure it exists.
*/
private void assureAclConfigFilesExist() {
final Path defaultAclFilePath = Paths.get(this.defaultAclFile);
if (!Files.exists(defaultAclFilePath)) {
try {
Files.createFile(defaultAclFilePath);
} catch (FileAlreadyExistsException e) {
// Maybe created by other threads
} catch (IOException e) {
log.error("Error in creating " + this.defaultAclFile, e);
throw new AclException(e.getMessage());
}
}
}
public void load(String aclFilePath) {
Map<String, PlainAccessResource> plainAccessResourceMap = new HashMap<>();
List<RemoteAddressStrategy> globalWhiteRemoteAddressStrategy = new ArrayList<>();
@@ -185,7 +211,8 @@ public class PlainPermissionManager {
JSONObject plainAclConfData = AclUtils.getYamlDataObject(aclFilePath,
JSONObject.class);
if (plainAclConfData == null || plainAclConfData.isEmpty()) {
throw new AclException(String.format("%s file is not data", aclFilePath));
log.warn("No data in {}, skip it", aclFilePath);
return;
}
log.info("Broker plain acl conf data is : ", plainAclConfData.toString());
JSONArray globalWhiteRemoteAddressesList = plainAclConfData.getJSONArray("globalWhiteRemoteAddresses");
@@ -212,7 +239,8 @@ public class PlainPermissionManager {
for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) {
PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig);
//AccessKey can not be defined in multiple ACL files
if (this.accessKeyTable.get(plainAccessResource.getAccessKey()) == null) {
String oldPath = this.accessKeyTable.get(plainAccessResource.getAccessKey());
if (oldPath == null || aclFilePath.equals(oldPath)) {
plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource);
this.accessKeyTable.put(plainAccessResource.getAccessKey(), aclFilePath);
}
@@ -245,7 +273,7 @@ public class PlainPermissionManager {
return this.dataVersionMap;
}
public Map<String, Object> updateAclConfigFileVersion(Map<String, Object> updateAclConfigMap) {
public Map<String, Object> updateAclConfigFileVersion(String aclFileName, Map<String, Object> updateAclConfigMap) {
Object dataVersions = updateAclConfigMap.get(AclConstants.CONFIG_DATA_VERSION);
DataVersion dataVersion = new DataVersion();
@@ -265,10 +293,8 @@ public class PlainPermissionManager {
versionElement.add(accountsMap);
updateAclConfigMap.put(AclConstants.CONFIG_DATA_VERSION, versionElement);
List<Map<String, Object>> accounts = (List<Map<String, Object>>) updateAclConfigMap.get(AclConstants.CONFIG_ACCOUNTS);
String accessKey = (String) accounts.get(0).get(AclConstants.CONFIG_ACCESS_KEY);
String aclFileName = accessKeyTable.get(accessKey);
dataVersionMap.put(aclFileName, dataVersion);
return updateAclConfigMap;
}
@@ -288,15 +314,23 @@ public class PlainPermissionManager {
String aclFileName = accessKeyTable.get(plainAccessConfig.getAccessKey());
Map<String, Object> aclAccessConfigMap = AclUtils.getYamlDataObject(aclFileName, Map.class);
List<Map<String, Object>> accounts = (List<Map<String, Object>>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS);
for (Map<String, Object> account : accounts) {
if (account.get(AclConstants.CONFIG_ACCESS_KEY).equals(plainAccessConfig.getAccessKey())) {
// Update acl access config elements
accounts.remove(account);
updateAccountMap = createAclAccessConfigMap(account, plainAccessConfig);
accounts.add(updateAccountMap);
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts);
break;
if (null != accounts) {
for (Map<String, Object> account : accounts) {
if (account.get(AclConstants.CONFIG_ACCESS_KEY).equals(plainAccessConfig.getAccessKey())) {
// Update acl access config elements
accounts.remove(account);
updateAccountMap = createAclAccessConfigMap(account, plainAccessConfig);
accounts.add(updateAccountMap);
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts);
break;
}
}
} else {
// Maybe deleted in file, add it back
accounts = new LinkedList<>();
updateAccountMap = createAclAccessConfigMap(null, plainAccessConfig);
accounts.add(updateAccountMap);
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts);
}
Map<String, PlainAccessResource> accountMap = aclPlainAccessResourceMap.get(aclFileName);
if (accountMap == null) {
@@ -314,7 +348,7 @@ public class PlainPermissionManager {
}
}
aclPlainAccessResourceMap.put(aclFileName, accountMap);
return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclAccessConfigMap));
return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclFileName, aclAccessConfigMap));
} else {
String fileName = defaultAclFile;
//Create acl access config elements on the default acl file
@@ -334,6 +368,10 @@ public class PlainPermissionManager {
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, new ArrayList<>());
}
List<Map<String, Object>> accounts = (List<Map<String, Object>>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS);
// When no accounts defined
if (null == accounts) {
accounts = new ArrayList<>();
}
accounts.add(createAclAccessConfigMap(null, plainAccessConfig));
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts);
accessKeyTable.put(plainAccessConfig.getAccessKey(), fileName);
@@ -346,7 +384,7 @@ public class PlainPermissionManager {
plainAccessResourceMap.put(plainAccessConfig.getAccessKey(), buildPlainAccessResource(plainAccessConfig));
aclPlainAccessResourceMap.put(fileName, plainAccessResourceMap);
}
return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(aclAccessConfigMap));
return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(defaultAclFile, aclAccessConfigMap));
}
}
@@ -409,7 +447,8 @@ public class PlainPermissionManager {
Map<String, Object> aclAccessConfigMap = AclUtils.getYamlDataObject(aclFileName,
Map.class);
if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) {
throw new AclException(String.format("the %s file is not found or empty", aclFileName));
log.warn("No data found in {} when deleting access config of {}", aclFileName, accesskey);
return true;
}
List<Map<String, Object>> accounts = (List<Map<String, Object>>) aclAccessConfigMap.get("accounts");
Iterator<Map<String, Object>> itemIterator = accounts.iterator();
@@ -417,8 +456,9 @@ public class PlainPermissionManager {
if (itemIterator.next().get(AclConstants.CONFIG_ACCESS_KEY).equals(accesskey)) {
// Delete the related acl config element
itemIterator.remove();
accessKeyTable.remove(accesskey);
aclAccessConfigMap.put(AclConstants.CONFIG_ACCOUNTS, accounts);
return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclAccessConfigMap));
return AclUtils.writeDataObject(aclFileName, updateAclConfigFileVersion(aclFileName, aclAccessConfigMap));
}
}
}
@@ -426,30 +466,7 @@ public class PlainPermissionManager {
}
public boolean updateGlobalWhiteAddrsConfig(List<String> globalWhiteAddrsList) {
if (globalWhiteAddrsList == null) {
log.error("Parameter value globalWhiteAddrsList is null,Please check your parameter");
return false;
}
Map<String, Object> aclAccessConfigMap = AclUtils.getYamlDataObject(defaultAclFile, Map.class);
if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) {
throw new AclException(String.format("the %s file is not found or empty", defaultAclFile));
}
List<String> globalWhiteRemoteAddrList = (List<String>) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS);
if (globalWhiteRemoteAddrList != null) {
globalWhiteRemoteAddrList.clear();
if (globalWhiteAddrsList != null) {
globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList);
}
// Update globalWhiteRemoteAddr element in memory map firstly
aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList);
return AclUtils.writeDataObject(defaultAclFile, updateAclConfigFileVersion(aclAccessConfigMap));
}
log.error("Users must ensure that the acl yaml config file has globalWhiteRemoteAddresses flag in the {} firstly", defaultAclFile);
return false;
return this.updateGlobalWhiteAddrsConfig(globalWhiteAddrsList, this.defaultAclFile);
}
public boolean updateGlobalWhiteAddrsConfig(List<String> globalWhiteAddrsList, String fileName) {
@@ -460,27 +477,19 @@ public class PlainPermissionManager {
File file = new File(fileName);
if (!file.exists() || file.isDirectory()) {
log.error("Parameter value fileName is not exist or is a directory,Please check your parameter");
log.error("Parameter value " + fileName + " is not exist or is a directory, please check your parameter");
return false;
}
Map<String, Object> aclAccessConfigMap = AclUtils.getYamlDataObject(fileName, Map.class);
if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) {
throw new AclException(String.format("the %s file is not found or empty", fileName));
}
List<String> globalWhiteRemoteAddrList = (List<String>) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS);
if (globalWhiteRemoteAddrList != null) {
globalWhiteRemoteAddrList.clear();
if (globalWhiteAddrsList != null) {
globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList);
}
// Update globalWhiteRemoteAddr element in memory map firstly
aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList);
return AclUtils.writeDataObject(fileName, updateAclConfigFileVersion(aclAccessConfigMap));
if (aclAccessConfigMap == null) {
aclAccessConfigMap = new HashMap<>();
log.info("No data in {}, create a new aclAccessConfigMap", fileName);
}
// Update globalWhiteRemoteAddr element in memory map firstly
aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, new ArrayList<>(globalWhiteAddrsList));
return AclUtils.writeDataObject(fileName, updateAclConfigFileVersion(fileName, aclAccessConfigMap));
log.error("Users must ensure that the acl yaml config file has globalWhiteRemoteAddresses flag in the {} firstly", fileName);
return false;
}
public AclConfig getAllAclConfig() {
@@ -494,7 +503,7 @@ public class PlainPermissionManager {
JSONObject plainAclConfData = AclUtils.getYamlDataObject(path,
JSONObject.class);
if (plainAclConfData == null || plainAclConfData.isEmpty()) {
throw new AclException(String.format("%s file is not data", path));
continue;
}
JSONArray globalWhiteAddrs = plainAclConfData.getJSONArray(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS);
if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) {
@@ -643,6 +652,9 @@ public class PlainPermissionManager {
// Check the white addr for accesskey
String aclFileName = accessKeyTable.get(plainAccessResource.getAccessKey());
PlainAccessResource ownedAccess = aclPlainAccessResourceMap.get(aclFileName).get(plainAccessResource.getAccessKey());
if (null == ownedAccess) {
throw new AclException(String.format("No PlainAccessResource for accessKey=%s", plainAccessResource.getAccessKey()));
}
if (ownedAccess.getRemoteAddressStrategy().match(plainAccessResource)) {
return;
}
@@ -0,0 +1,396 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.acl.plain;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.AclConstants;
import org.apache.rocketmq.acl.common.AclException;
import org.apache.rocketmq.acl.common.AclUtils;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.common.AclConfig;
import org.apache.rocketmq.common.PlainAccessConfig;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* <p> In this class, we'll test the following scenarios, each containing several consecutive operations on ACL,
* <p> like updating and deleting ACL, changing config files and checking validations.
* <p> Case 1: Only conf/plain_acl.yml exists;
* <p> Case 2: Only conf/acl/plain_acl.yml exists;
* <p> Case 3: Both conf/plain_acl.yml and conf/acl/plain_acl.yml exists.
*/
public class PlainAccessControlFlowTest {
public static final String DEFAULT_TOPIC = "topic-acl";
public static final String DEFAULT_GROUP = "GID_acl";
public static final String DEFAULT_PRODUCER_AK = "ak11111";
public static final String DEFAULT_PRODUCER_SK = "1234567";
public static final String DEFAULT_CONSUMER_SK = "7654321";
public static final String DEFAULT_CONSUMER_AK = "ak22222";
public static final String DEFAULT_GLOBAL_WHITE_ADDR = "172.16.123.123";
public static final List<String> DEFAULT_GLOBAL_WHITE_ADDRS_LIST = Arrays.asList(DEFAULT_GLOBAL_WHITE_ADDR);
public static final Path EMPTY_ACL_FOLDER_PLAIN_ACL_YML_PATH = Paths.get("src/test/resources/empty_acl_folder_conf/conf/plain_acl.yml");
private static final Path EMPTY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH = Paths.get("src/test/resources/empty_acl_folder_conf/conf/plain_acl.yml.bak");
public static final Path ONLY_ACL_FOLDER_DELETE_YML_PATH = Paths.get("src/test/resources/only_acl_folder_conf/conf/plain_acl.yml");
private static final Path ONLY_ACL_FOLDER_PLAIN_ACL_YML_PATH = Paths.get("src/test/resources/only_acl_folder_conf/conf/acl/plain_acl.yml");
private static final Path ONLY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH = Paths.get("src/test/resources/only_acl_folder_conf/conf/acl/plain_acl.yml.bak");
private static final Path BOTH_ACL_FOLDER_PLAIN_ACL_YML_PATH = Paths.get("src/test/resources/both_acl_file_folder_conf/conf/acl/plain_acl.yml");
private static final Path BOTH_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH = Paths.get("src/test/resources/both_acl_file_folder_conf/conf/acl/plain_acl.yml.bak");
private static final Path BOTH_CONF_FOLDER_PLAIN_ACL_YML_PATH = Paths.get("src/test/resources/both_acl_file_folder_conf/conf/plain_acl.yml");
private static final Path BOTH_CONF_FOLDER_PLAIN_ACL_YML_BAK_PATH = Paths.get("src/test/resources/both_acl_file_folder_conf/conf/plain_acl.yml.bak");
private boolean isCheckCase1 = false;
private boolean isCheckCase2 = false;
private boolean isCheckCase3 = false;
/**
* backup ACL config files
*
* @throws IOException
*/
@Before
public void prepare() throws IOException {
Files.copy(EMPTY_ACL_FOLDER_PLAIN_ACL_YML_PATH,
EMPTY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
StandardCopyOption.REPLACE_EXISTING);
Files.copy(ONLY_ACL_FOLDER_PLAIN_ACL_YML_PATH,
ONLY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
StandardCopyOption.REPLACE_EXISTING);
Files.copy(BOTH_ACL_FOLDER_PLAIN_ACL_YML_PATH,
BOTH_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
StandardCopyOption.REPLACE_EXISTING);
Files.copy(BOTH_CONF_FOLDER_PLAIN_ACL_YML_PATH,
BOTH_CONF_FOLDER_PLAIN_ACL_YML_BAK_PATH,
StandardCopyOption.REPLACE_EXISTING);
}
/**
* restore ACL config files
*
* @throws IOException
*/
@After
public void restore() throws IOException {
if (this.isCheckCase1) {
Files.copy(EMPTY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
EMPTY_ACL_FOLDER_PLAIN_ACL_YML_PATH,
StandardCopyOption.REPLACE_EXISTING);
}
if (this.isCheckCase2) {
Files.copy(ONLY_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
ONLY_ACL_FOLDER_PLAIN_ACL_YML_PATH,
StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(ONLY_ACL_FOLDER_DELETE_YML_PATH);
}
if (this.isCheckCase3) {
Files.copy(BOTH_ACL_FOLDER_PLAIN_ACL_YML_BAK_PATH,
BOTH_ACL_FOLDER_PLAIN_ACL_YML_PATH,
StandardCopyOption.REPLACE_EXISTING);
Files.copy(BOTH_CONF_FOLDER_PLAIN_ACL_YML_BAK_PATH,
BOTH_CONF_FOLDER_PLAIN_ACL_YML_PATH,
StandardCopyOption.REPLACE_EXISTING);
}
}
@Test
public void testEmptyAclFolderCase() throws NoSuchFieldException, IllegalAccessException {
this.isCheckCase1 = true;
System.setProperty("rocketmq.home.dir", Paths.get("src/test/resources/empty_acl_folder_conf").toString());
PlainAccessValidator plainAccessValidator = new PlainAccessValidator();
checkDefaultAclFileExists(plainAccessValidator);
testValidationAfterConsecutiveUpdates(plainAccessValidator);
testValidationAfterConfigFileChanged(plainAccessValidator);
}
@Test
public void testOnlyAclFolderCase() throws NoSuchFieldException, IllegalAccessException {
this.isCheckCase2 = true;
System.setProperty("rocketmq.home.dir", Paths.get("src/test/resources/only_acl_folder_conf").toString());
PlainAccessValidator plainAccessValidator = new PlainAccessValidator();
checkDefaultAclFileExists(plainAccessValidator);
testValidationAfterConsecutiveUpdates(plainAccessValidator);
testValidationAfterConfigFileChanged(plainAccessValidator);
}
@Test
public void testBothAclFileAndFolderCase() throws NoSuchFieldException, IllegalAccessException {
this.isCheckCase3 = true;
System.setProperty("rocketmq.home.dir", Paths.get("src/test/resources/both_acl_file_folder_conf").toString());
PlainAccessValidator plainAccessValidator = new PlainAccessValidator();
checkDefaultAclFileExists(plainAccessValidator);
testValidationAfterConsecutiveUpdates(plainAccessValidator);
testValidationAfterConfigFileChanged(plainAccessValidator);
}
private void testValidationAfterConfigFileChanged(PlainAccessValidator plainAccessValidator) throws NoSuchFieldException, IllegalAccessException {
PlainAccessConfig producerAccessConfig = generateProducerAccessConfig();
PlainAccessConfig consumerAccessConfig = generateConsumerAccessConfig();
List<PlainAccessConfig> plainAccessConfigList = new LinkedList<>();
plainAccessConfigList.add(producerAccessConfig);
plainAccessConfigList.add(consumerAccessConfig);
Map<String, Object> ymlMap = new HashMap<>();
ymlMap.put(AclConstants.CONFIG_ACCOUNTS, plainAccessConfigList);
// write prepared PlainAccessConfigs to file
final String aclConfigFile = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml";
AclUtils.writeDataObject(aclConfigFile, ymlMap);
loadConfigFile(plainAccessValidator, aclConfigFile);
// check if added successfully
final AclConfig allAclConfig = plainAccessValidator.getAllAclConfig();
final List<PlainAccessConfig> plainAccessConfigs = allAclConfig.getPlainAccessConfigs();
checkPlainAccessConfig(producerAccessConfig, plainAccessConfigs);
checkPlainAccessConfig(consumerAccessConfig, plainAccessConfigs);
//delete consumer account
plainAccessConfigList.remove(consumerAccessConfig);
AclUtils.writeDataObject(aclConfigFile, ymlMap);
loadConfigFile(plainAccessValidator, aclConfigFile);
// sending messages will be successful using prepared credentials
SessionCredentials producerCredential = new SessionCredentials(DEFAULT_PRODUCER_AK, DEFAULT_PRODUCER_SK);
AclClientRPCHook producerHook = new AclClientRPCHook(producerCredential);
validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
// consuming messages will be failed for account has been deleted
SessionCredentials consumerCredential = new SessionCredentials(DEFAULT_CONSUMER_AK, DEFAULT_CONSUMER_SK);
AclClientRPCHook consumerHook = new AclClientRPCHook(consumerCredential);
boolean isConsumeFailed = false;
try {
validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator);
} catch (AclException e) {
isConsumeFailed = true;
}
Assert.assertTrue("Message should not be consumed after account deleted", isConsumeFailed);
}
private void testValidationAfterConsecutiveUpdates(PlainAccessValidator plainAccessValidator) throws NoSuchFieldException, IllegalAccessException {
PlainAccessConfig producerAccessConfig = generateProducerAccessConfig();
plainAccessValidator.updateAccessConfig(producerAccessConfig);
PlainAccessConfig consumerAccessConfig = generateConsumerAccessConfig();
plainAccessValidator.updateAccessConfig(consumerAccessConfig);
plainAccessValidator.updateGlobalWhiteAddrsConfig(DEFAULT_GLOBAL_WHITE_ADDRS_LIST);
// check if the above config updated successfully
final AclConfig allAclConfig = plainAccessValidator.getAllAclConfig();
final List<PlainAccessConfig> plainAccessConfigs = allAclConfig.getPlainAccessConfigs();
checkPlainAccessConfig(producerAccessConfig, plainAccessConfigs);
checkPlainAccessConfig(consumerAccessConfig, plainAccessConfigs);
Assert.assertEquals(DEFAULT_GLOBAL_WHITE_ADDRS_LIST, allAclConfig.getGlobalWhiteAddrs());
// check sending and consuming messages
SessionCredentials producerCredential = new SessionCredentials(DEFAULT_PRODUCER_AK, DEFAULT_PRODUCER_SK);
AclClientRPCHook producerHook = new AclClientRPCHook(producerCredential);
validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
SessionCredentials consumerCredential = new SessionCredentials(DEFAULT_CONSUMER_AK, DEFAULT_CONSUMER_SK);
AclClientRPCHook consumerHook = new AclClientRPCHook(consumerCredential);
validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator);
// load from file
loadConfigFile(plainAccessValidator,
System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml");
SessionCredentials unmatchedCredential = new SessionCredentials("non_exists_sk", "non_exists_sk");
AclClientRPCHook dummyHook = new AclClientRPCHook(unmatchedCredential);
validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator);
validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator);
validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, dummyHook, DEFAULT_GLOBAL_WHITE_ADDR, plainAccessValidator);
//recheck after reloading
validateSendMessage(RequestCode.SEND_MESSAGE, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
validateSendMessage(RequestCode.SEND_MESSAGE_V2, DEFAULT_TOPIC, producerHook, "", plainAccessValidator);
validatePullMessage(DEFAULT_TOPIC, DEFAULT_GROUP, consumerHook, "", plainAccessValidator);
}
private void loadConfigFile(PlainAccessValidator plainAccessValidator, String configFileName) throws NoSuchFieldException, IllegalAccessException {
Class clazz = PlainAccessValidator.class;
Field f = clazz.getDeclaredField("aclPlugEngine");
f.setAccessible(true);
PlainPermissionManager aclPlugEngine = (PlainPermissionManager) f.get(plainAccessValidator);
aclPlugEngine.load(configFileName);
}
private PlainAccessConfig generateConsumerAccessConfig() {
PlainAccessConfig plainAccessConfig2 = new PlainAccessConfig();
String accessKey2 = DEFAULT_CONSUMER_AK;
String secretKey2 = DEFAULT_CONSUMER_SK;
plainAccessConfig2.setAccessKey(accessKey2);
plainAccessConfig2.setSecretKey(secretKey2);
plainAccessConfig2.setAdmin(false);
plainAccessConfig2.setDefaultTopicPerm(AclConstants.DENY);
plainAccessConfig2.setDefaultGroupPerm(AclConstants.DENY);
plainAccessConfig2.setTopicPerms(Arrays.asList(DEFAULT_TOPIC + "=" + AclConstants.SUB));
plainAccessConfig2.setGroupPerms(Arrays.asList(DEFAULT_GROUP + "=" + AclConstants.SUB));
return plainAccessConfig2;
}
private PlainAccessConfig generateProducerAccessConfig() {
PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
String accessKey = DEFAULT_PRODUCER_AK;
String secretKey = DEFAULT_PRODUCER_SK;
plainAccessConfig.setAccessKey(accessKey);
plainAccessConfig.setSecretKey(secretKey);
plainAccessConfig.setAdmin(false);
plainAccessConfig.setDefaultTopicPerm(AclConstants.DENY);
plainAccessConfig.setDefaultGroupPerm(AclConstants.DENY);
plainAccessConfig.setTopicPerms(Arrays.asList(DEFAULT_TOPIC + "=" + AclConstants.PUB));
return plainAccessConfig;
}
public void validatePullMessage(String topic,
String group,
AclClientRPCHook aclClientRPCHook,
String remoteAddr,
PlainAccessValidator plainAccessValidator) {
PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader();
pullMessageRequestHeader.setTopic(topic);
pullMessageRequestHeader.setConsumerGroup(group);
RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE,
pullMessageRequestHeader);
aclClientRPCHook.doBeforeRequest(remoteAddr, remotingCommand);
ByteBuffer buf = remotingCommand.encodeHeader();
buf.getInt();
buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
buf.position(0);
try {
PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(
RemotingCommand.decode(buf), remoteAddr);
plainAccessValidator.validate(accessResource);
} catch (RemotingCommandException e) {
e.printStackTrace();
Assert.fail("Should not throw RemotingCommandException");
}
}
public void validateSendMessage(int requestCode,
String topic,
AclClientRPCHook aclClientRPCHook,
String remoteAddr,
PlainAccessValidator plainAccessValidator) {
SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
messageRequestHeader.setTopic(topic);
RemotingCommand remotingCommand;
if (RequestCode.SEND_MESSAGE == requestCode) {
remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
} else {
remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2,
SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(messageRequestHeader));
}
aclClientRPCHook.doBeforeRequest(remoteAddr, remotingCommand);
ByteBuffer buf = remotingCommand.encodeHeader();
buf.getInt();
buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
buf.position(0);
try {
PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(
RemotingCommand.decode(buf), remoteAddr);
System.out.println(accessResource.getWhiteRemoteAddress());
plainAccessValidator.validate(accessResource);
} catch (RemotingCommandException e) {
e.printStackTrace();
Assert.fail("Should not throw RemotingCommandException");
}
}
private void checkPlainAccessConfig(final PlainAccessConfig plainAccessConfig, final List<PlainAccessConfig> plainAccessConfigs) {
for (PlainAccessConfig config : plainAccessConfigs) {
if (config.getAccessKey().equals(plainAccessConfig.getAccessKey())) {
Assert.assertEquals(plainAccessConfig.getSecretKey(), config.getSecretKey());
Assert.assertEquals(plainAccessConfig.isAdmin(), config.isAdmin());
Assert.assertEquals(plainAccessConfig.getDefaultGroupPerm(), config.getDefaultGroupPerm());
Assert.assertEquals(plainAccessConfig.getDefaultGroupPerm(), config.getDefaultGroupPerm());
Assert.assertEquals(plainAccessConfig.getWhiteRemoteAddress(), config.getWhiteRemoteAddress());
if (null != plainAccessConfig.getTopicPerms()) {
Assert.assertNotNull(config.getTopicPerms());
Assert.assertTrue(config.getTopicPerms().containsAll(plainAccessConfig.getTopicPerms()));
}
if (null != plainAccessConfig.getGroupPerms()) {
Assert.assertNotNull(config.getGroupPerms());
Assert.assertTrue(config.getGroupPerms().containsAll(plainAccessConfig.getGroupPerms()));
}
}
}
}
private void checkDefaultAclFileExists(PlainAccessValidator plainAccessValidator) {
boolean isExists = Files.exists(Paths.get(System.getProperty("rocketmq.home.dir")
+ File.separator + "conf/plain_acl.yml"));
Assert.assertTrue("default acl config file should exist", isExists);
}
}
@@ -19,6 +19,7 @@ package org.apache.rocketmq.acl.plain;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
@@ -506,7 +507,7 @@ public class PlainAccessValidatorTest {
}
@Test
public void updateAccessAclYamlConfigTest() throws InterruptedException{
public void updateAccessAclYamlConfigTest() throws InterruptedException {
String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/plain_acl.yml";
Map<String, Object> backUpAclConfigMap = AclUtils.getYamlDataObject(targetFileName, Map.class);
@@ -902,4 +903,61 @@ public class PlainAccessValidatorTest {
plainAccessValidator.deleteAccessConfig(accessKey);
AclUtils.writeDataObject(targetFileName, backUpAclConfigMap);
}
@Test
public void deleteAccessAclToEmptyTest() {
System.setProperty("rocketmq.acl.plain.file", "/conf/empty.yml");
PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
plainAccessConfig.setAccessKey("deleteAccessAclToEmpty");
plainAccessConfig.setSecretKey("12345678");
PlainAccessValidator plainAccessValidator = new PlainAccessValidator();
plainAccessValidator.updateAccessConfig(plainAccessConfig);
boolean success = plainAccessValidator.deleteAccessConfig("deleteAccessAclToEmpty");
System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml");
Assert.assertTrue(success);
}
@Test
public void testValidateAfterUpdateAccessConfig() throws NoSuchFieldException, IllegalAccessException {
String targetFileName = System.getProperty("rocketmq.home.dir") + File.separator + "conf/update.yml";
System.setProperty("rocketmq.acl.plain.file", "conf/update.yml");
PlainAccessValidator plainAccessValidator = new PlainAccessValidator();
PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
String accessKey = "updateAccessConfig";
String secretKey = "123456789111";
plainAccessConfig.setAccessKey(accessKey);
plainAccessConfig.setSecretKey(secretKey);
plainAccessConfig.setAdmin(true);
// update
plainAccessValidator.updateAccessConfig(plainAccessConfig);
// call load
Class clazz = PlainAccessValidator.class;
Field f = clazz.getDeclaredField("aclPlugEngine");
f.setAccessible(true);
PlainPermissionManager aclPlugEngine = (PlainPermissionManager) f.get(plainAccessValidator);
aclPlugEngine.load(targetFileName);
// call validate
PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader();
pullMessageRequestHeader.setTopic("topicC");
pullMessageRequestHeader.setConsumerGroup("consumerGroupA");
RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader);
AclClientRPCHook aclClient = new AclClientRPCHook(new SessionCredentials(accessKey, secretKey));
aclClient.doBeforeRequest("", remotingCommand);
ByteBuffer buf = remotingCommand.encodeHeader();
buf.getInt();
buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
buf.position(0);
try {
PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "1.1.1.1:9876");
plainAccessValidator.validate(accessResource);
} catch (RemotingCommandException e) {
e.printStackTrace();
Assert.fail("Should not throw IOException");
} finally {
System.setProperty("rocketmq.acl.plain.file", "conf/plain_acl.yml");
}
}
}
@@ -0,0 +1,39 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## no global white addresses in this file, define them in ../plain_acl.yml
accounts:
- accessKey: RocketMQ
secretKey: 12345678
whiteRemoteAddress: 192.168.0.*
admin: false
defaultTopicPerm: DENY
defaultGroupPerm: SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
# the group should convert to retry topic
- groupA=DENY
- groupB=SUB
- groupC=SUB
- accessKey: rocketmq2
secretKey: 12345678
whiteRemoteAddress: 192.168.1.*
# if it is admin, it could access all resources
admin: true
@@ -0,0 +1,21 @@
# 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.
## suggested format
globalWhiteRemoteAddresses:
- 10.10.103.*
- 192.168.0.*
@@ -0,0 +1,19 @@
# 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.
globalWhiteRemoteAddresses:
- 10.10.103.*
- 192.168.0.*
@@ -0,0 +1,39 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## no global white addresses in this file, define them in ../plain_acl.yml
accounts:
- accessKey: RocketMQ
secretKey: 12345678
whiteRemoteAddress: 192.168.0.*
admin: false
defaultTopicPerm: DENY
defaultGroupPerm: SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
# the group should convert to retry topic
- groupA=DENY
- groupB=SUB
- groupC=SUB
- accessKey: rocketmq2
secretKey: 12345678
whiteRemoteAddress: 192.168.1.*
# if it is admin, it could access all resources
admin: true
@@ -28,6 +28,10 @@ import org.apache.rocketmq.remoting.protocol.RemotingSerializable;
public class LmqConsumerOffsetManager extends ConsumerOffsetManager {
private ConcurrentHashMap<String, Long> lmqOffsetTable = new ConcurrentHashMap<>(512);
public LmqConsumerOffsetManager() {
}
public LmqConsumerOffsetManager(BrokerController brokerController) {
super(brokerController);
}
@@ -73,6 +73,33 @@ public class LmqConsumerOffsetManagerTest {
assertThat(offset1).isEqualTo(-1L);
}
@Test
public void testOffsetManage1() {
LmqConsumerOffsetManager lmqConsumerOffsetManager = new LmqConsumerOffsetManager(brokerController);
String lmqTopicName = "%LMQ%1111";
String lmqGroupName = "%LMQ%GID_test";
lmqConsumerOffsetManager.commitOffset("127.0.0.1", lmqGroupName, lmqTopicName, 0, 10L);
lmqTopicName = "%LMQ%1222";
lmqGroupName = "%LMQ%GID_test222";
lmqConsumerOffsetManager.commitOffset("127.0.0.1", lmqGroupName, lmqTopicName, 0, 10L);
lmqConsumerOffsetManager.commitOffset("127.0.0.1","GID_test1", "MqttTest",0, 10L);
String json = lmqConsumerOffsetManager.encode(true);
LmqConsumerOffsetManager lmqConsumerOffsetManager1 = new LmqConsumerOffsetManager(brokerController);
lmqConsumerOffsetManager1.decode(json);
assertThat(lmqConsumerOffsetManager1.getOffsetTable().size()).isEqualTo(1);
assertThat(lmqConsumerOffsetManager1.getLmqOffsetTable().size()).isEqualTo(2);
}
@After
public void destroy() {
UtilAll.deleteFile(new File(new MessageStoreConfig().getStorePathRootDir()));
@@ -301,8 +301,7 @@ public class MQClientAPIImpl {
}
public void createSubscriptionGroup(final String addr, final SubscriptionGroupConfig config,
final long timeoutMillis)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
final long timeoutMillis) throws RemotingException, InterruptedException, MQClientException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null);
byte[] body = RemotingSerializable.encode(config);
@@ -675,8 +674,8 @@ public class MQClientAPIImpl {
retryBrokerName = instance.getBrokerNameFromMessageQueue(mqChosen);
}
String addr = instance.findBrokerAddressInPublish(retryBrokerName);
log.warn(String.format("async send msg by retry {} times. topic={}, brokerAddr={}, brokerName={}", tmp, msg.getTopic(), addr,
retryBrokerName), e);
log.warn("async send msg by retry {} times. topic={}, brokerAddr={}, brokerName={}", tmp, msg.getTopic(), addr,
retryBrokerName, e);
try {
request.setOpaque(RemotingCommand.createNewRequestId());
sendMessageAsync(addr, retryBrokerName, msg, timeoutMillis, request, sendCallback, topicPublishInfo, instance,
@@ -71,7 +71,7 @@ public class ConsumeMessageConcurrentlyService implements ConsumeMessageService
String consumeThreadPrefix = null;
if (consumerGroup.length() > 100) {
consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup.substring(0, 100)).append("_").toString();
consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup, 0, 100).append("_").toString();
} else {
consumeThreadPrefix = new StringBuilder("ConsumeMessageThread_").append(consumerGroup).append("_").toString();
}
@@ -33,7 +33,7 @@ public class ThreadLocalIndexTest {
public void testIncrementAndGet2() throws Exception {
ThreadLocalIndex localIndex = new ThreadLocalIndex();
int initialVal = localIndex.incrementAndGet();
assertThat(initialVal >= 0);
assertThat(initialVal >= 0).isTrue();
}
}
@@ -640,12 +640,6 @@ public class MQVersion {
V4_9_9_SNAPSHOT,
V4_9_9,
V5_0_0_PREVIEW_SNAPSHOT,
V5_0_0_PREVIEW,
V5_0_0_ALPHA_SNAPSHOT,
V5_0_0_ALPHA,
V5_0_0_SNAPSHOT,
V5_0_0,
@@ -58,6 +58,11 @@ public class TopicValidator {
SYSTEM_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SCHEDULE_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_HALF_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_OP_HALF_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SELF_TEST_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT);
// regex: ^[%|a-zA-Z0-9_-]+$
// %
+6 -6
View File
@@ -49,7 +49,7 @@ The broker[broker-a,192.169.1.2:10911] boot success...
**1)启动 NameServer**
```shell
### 第一步先启动broker
### 第一步先启动namesrv
$ nohup sh mqnamesrv &
### 验证namesrv是否启动成功
@@ -69,14 +69,14 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker
...
```
上面显示的boot命令用于单个NameServer的情况。对于多个NameServer的集群,broker boot命令中-n参数后面的地址列表用分号隔开,例如 192.168.1.1 : 9876; 192.161.2 : 9876
上面显示的启动命令用于单个NameServer的情况。对于多个NameServer的集群,broker 启动命令中-n参数后面的地址列表用分号隔开,例如 192.168.1.1:9876;192.161.2:9876
### 3 多Master多Slave模式-异步复制
每个主节点配置多个从节点,多对主从。HA采用异步复制,主节点和从节点之间有短消息延迟(毫秒)。这种模式的优缺点如下:
- 优点:
1. 即使磁盘损坏,也会丢失极少的消息,不影响消息的实时性能。
- 优点:
1. 即使磁盘损坏,也会丢失极少的消息,不影响消息的实时性能。
2. 同时,当主节点宕机时,消费者仍然可以消费从节点的消息,这个过程对应用本身是透明的,不需要人为干预。
3. 性能几乎与多Master模式一样高。
- 缺点:
@@ -87,7 +87,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker
**1)启动 NameServer**
```shell
### 第一步先启动broker
### 第一步先启动namesrv
$ nohup sh mqnamesrv &
### 验证namesrv是否启动成功
@@ -132,7 +132,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broke
**1)启动NameServer**
```shell
### 第一步启动broker
### 第一步启动namesrv
$ nohup sh mqnamesrv &
### 验证namesrv是否启动成功
+1 -1
View File
@@ -184,7 +184,7 @@ msgId一定是全局唯一标识符,但是实际使用中,可能会存在相
| brokerIP1 | 网卡的 InetAddress | 当前 broker 监听的 IP |
| brokerIP2 | 跟 brokerIP1 一样 | 存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步 |
| brokerName | null | broker 的名称 |
| brokerClusterName | DefaultCluster | 本 broker 所属的 Cluser 名称 |
| brokerClusterName | DefaultCluster | 本 broker 所属的 Cluster 名称 |
| brokerId | 0 | broker id, 0 表示 master, 其他的正整数表示 slave |
| storePathRootDir | $HOME/store/ | 存储根路径 |
| storePathCommitLog | $HOME/store/commitlog/ | 存储 commit log 的路径 |
@@ -29,7 +29,7 @@ public class PushConsumerWithNamespace {
msgs.stream().forEach((msg) -> {
System.out.printf("Msg topic is:%s, MsgId is:%s, reconsumeTimes is:%s%n", msg.getTopic() , msg.getMsgId(), msg.getReconsumeTimes());
});
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
defaultMQPushConsumer.start();
@@ -143,12 +143,9 @@ public class NamesrvStartup {
System.exit(-3);
}
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
@Override
public Void call() throws Exception {
controller.shutdown();
return null;
}
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, (Callable<Void>) () -> {
controller.shutdown();
return null;
}));
controller.start();
@@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.namesrv;
import java.util.Properties;
import org.apache.commons.cli.Options;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class NamesrvStartupTest {
@Mock
private NamesrvController namesrvController;
@Mock
private Options options;
@Before
public void setUp() throws Exception {
Mockito.when(namesrvController.initialize()).thenReturn(true);
}
@Test
public void testStart() throws Exception {
NamesrvController controller = NamesrvStartup.start(namesrvController);
Assert.assertNotNull(controller);
}
@Test
public void testShutdown() {
NamesrvStartup.shutdown(namesrvController);
Mockito.verify(namesrvController).shutdown();
}
@Test
public void testBuildCommandlineOptions() {
Options options = NamesrvStartup.buildCommandlineOptions(this.options);
Assert.assertNotNull(options);
}
@Test
public void testGetProperties() {
Properties properties = NamesrvStartup.getProperties();
Assert.assertNull(properties);
}
}
@@ -29,7 +29,8 @@ public enum LanguageCode {
HTTP((byte) 8),
GO((byte) 9),
PHP((byte) 10),
OMS((byte) 11);
OMS((byte) 11),
RUST((byte) 12);
private byte code;
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.remoting.protocol;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class LanguageCodeTest {
@Test
public void testLanguageCodeRust() {
LanguageCode code = LanguageCode.valueOf((byte) 12);
assertThat(code).isEqualTo(LanguageCode.RUST);
code = LanguageCode.valueOf("RUST");
assertThat(code).isEqualTo(LanguageCode.RUST);
}
}
@@ -17,11 +17,15 @@
package org.apache.rocketmq.store;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.rocketmq.common.ServiceThread;
@@ -39,6 +43,12 @@ public class StoreStatsService extends ServiceThread {
"[<=0ms]", "[0~10ms]", "[10~50ms]", "[50~100ms]", "[100~200ms]", "[200~500ms]", "[500ms~1s]", "[1~2s]", "[2~3s]", "[3~4s]", "[4~5s]", "[5~10s]", "[10s~]",
};
//The rule to define buckets
private static final Map<Integer, Integer> PUT_MESSAGE_ENTIRE_TIME_BUCKETS = new TreeMap<>();
//buckets
private TreeMap<Long/*bucket*/, LongAdder/*times*/> buckets = new TreeMap<>();
private Map<Long/*bucket*/, LongAdder/*times*/> lastBuckets = new TreeMap<>();
private static int printTPSInterval = 60 * 1;
private final LongAdder putMessageFailedTimes = new LongAdder();
@@ -72,9 +82,66 @@ public class StoreStatsService extends ServiceThread {
private long lastPrintTimestamp = System.currentTimeMillis();
public StoreStatsService() {
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(1,20); //0-20
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(2,15); //20-50
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(5,10); //50-100
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(10,10); //100-200
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(50,6); //200-500
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(100,5); //500-1000
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.put(1000,9); //1s-10s
this.initPutMessageTimeBuckets();
this.initPutMessageDistributeTime();
}
public void initPutMessageTimeBuckets() {
TreeMap<Long, LongAdder> nextBuckets = new TreeMap<>();
AtomicLong index = new AtomicLong(0);
PUT_MESSAGE_ENTIRE_TIME_BUCKETS.forEach((interval, times) -> {
for (int i = 0; i < times; i++) {
nextBuckets.put(index.addAndGet(interval), new LongAdder());
}
});
nextBuckets.put(Long.MAX_VALUE, new LongAdder());
this.lastBuckets = this.buckets;
this.buckets = nextBuckets;
}
public void incPutMessageEntireTime(long value) {
Map.Entry<Long, LongAdder> targetBucket = buckets.ceilingEntry(value);
if (targetBucket != null) {
targetBucket.getValue().add(1);
}
}
public double findPutMessageEntireTimePX(double px) {
Map<Long, LongAdder> lastBuckets = this.lastBuckets;
long start = System.currentTimeMillis();
double result = 0.0;
long totalRequest = lastBuckets.values().stream().mapToLong(LongAdder::longValue).sum();
long pxIndex = (long) (totalRequest * px);
long passCount = 0;
List<Long> bucketValue = new ArrayList<>(lastBuckets.keySet());
for (int i = 0; i < bucketValue.size(); i++) {
long count = lastBuckets.get(bucketValue.get(i)).longValue();
if (pxIndex <= passCount + count) {
long relativeIndex = pxIndex - passCount;
if (i == 0) {
result = count == 0 ? 0 : bucketValue.get(i) * relativeIndex / (double)count;
} else {
long lastBucket = bucketValue.get(i - 1);
result = lastBucket + (count == 0 ? 0 : (bucketValue.get(i) - lastBucket) * relativeIndex / (double)count);
}
break;
} else {
passCount += count;
}
}
log.info("findPutMessageEntireTimePX {}={}ms cost {}ms", px, String.format("%.2f", result), System.currentTimeMillis() - start);
return result;
}
private LongAdder[] initPutMessageDistributeTime() {
LongAdder[] next = new LongAdder[13];
for (int i = 0; i < next.length; i++) {
@@ -93,6 +160,7 @@ public class StoreStatsService extends ServiceThread {
}
public void setPutMessageEntireTimeMax(long value) {
this.incPutMessageEntireTime(value);
final LongAdder[] times = this.putMessageDistributeTime;
if (null == times)
@@ -443,6 +511,8 @@ public class StoreStatsService extends ServiceThread {
result.put("getMissTps", this.getGetMissTps());
result.put("getTotalTps", this.getGetTotalTps());
result.put("getTransferedTps", this.getGetTransferedTps());
result.put("putLatency99", String.format("%.2f", this.findPutMessageEntireTimePX(0.99)));
result.put("putLatency999", String.format("%.2f", this.findPutMessageEntireTimePX(0.999)));
return result;
}
@@ -524,7 +594,9 @@ public class StoreStatsService extends ServiceThread {
sb.append(String.format("%s:%d", PUT_MESSAGE_ENTIRE_TIME_MAX_DESC[i], value));
sb.append(" ");
}
this.initPutMessageTimeBuckets();
this.findPutMessageEntireTimePX(0.99);
this.findPutMessageEntireTimePX(0.999);
log.info("[PAGECACHERT] TotalPut {}, PutMessageDistributeTime {}", totalPut, sb.toString());
}
}
@@ -45,6 +45,7 @@ import org.apache.rocketmq.store.PutMessageContext;
import org.apache.rocketmq.store.SelectMappedBufferResult;
import org.apache.rocketmq.store.TransientStorePool;
import org.apache.rocketmq.store.config.FlushDiskType;
import org.apache.rocketmq.store.config.MessageStoreConfig;
import org.apache.rocketmq.store.util.LibC;
import sun.nio.ch.DirectBuffer;
@@ -91,14 +92,25 @@ public class DefaultMappedFile extends AbstractMappedFile {
public static void ensureDirOK(final String dirName) {
if (dirName != null) {
File f = new File(dirName);
if (!f.exists()) {
boolean result = f.mkdirs();
log.info(dirName + " mkdir " + (result ? "OK" : "Failed"));
if (dirName.contains(MessageStoreConfig.MULTI_PATH_SPLITTER)) {
String[] dirs = dirName.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER);
for (String dir : dirs) {
createDirIfNotExist(dir);
}
} else {
createDirIfNotExist(dirName);
}
}
}
private static void createDirIfNotExist(String dirName) {
File f = new File(dirName);
if (!f.exists()) {
boolean result = f.mkdirs();
log.info(dirName + " mkdir " + (result ? "OK" : "Failed"));
}
}
public static void clean(final ByteBuffer buffer) {
if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0)
return;
@@ -89,4 +89,17 @@ public class StoreStatsServiceTest {
}
}
@Test
public void findPutMessageEntireTimePXTest() {
final StoreStatsService storeStatsService = new StoreStatsService();
for (int i = 1; i <= 1000; i++) {
for (int j = 0; j < i; j++) {
storeStatsService.incPutMessageEntireTime(i);
}
}
storeStatsService.initPutMessageTimeBuckets();
System.out.println(storeStatsService.findPutMessageEntireTimePX(0.99));
System.out.println(storeStatsService.findPutMessageEntireTimePX(0.999));
}
}
@@ -16,6 +16,11 @@
*/
package org.apache.rocketmq.store;
import io.openmessaging.storage.dledger.store.file.DefaultMmapFile;
import io.openmessaging.storage.dledger.store.file.MmapFile;
import java.io.IOException;
import java.util.List;
import org.apache.commons.lang3.SystemUtils;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.store.index.IndexFile;
@@ -86,4 +91,14 @@ public class StoreTestUtil {
indexService.flush(f);
}
}
public static void releaseMmapFilesOnWindows(List<MmapFile> mappedFiles) throws IOException {
if (!SystemUtils.IS_OS_WINDOWS) {
return;
}
for (final MmapFile mappedFile : mappedFiles) {
DefaultMmapFile.clean(mappedFile.getMappedByteBuffer());
mappedFile.getFileChannel().close();
}
}
}
@@ -40,6 +40,8 @@ import org.apache.rocketmq.store.PutMessageStatus;
import org.junit.Assert;
import org.junit.Test;
import static org.apache.rocketmq.store.StoreTestUtil.releaseMmapFilesOnWindows;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
@@ -67,6 +69,7 @@ public class DLedgerCommitlogTest extends MessageStoreTestBase {
Assert.assertEquals(0, messageStore.dispatchBehindBytes());
doGetMessages(messageStore, topic, 0, 2000, 0);
messageStore.shutdown();
releaseMmapFilesOnWindows(dLedgerMmapFileStore.getDataFileList().getMappedFiles());
}
{
@@ -83,6 +86,7 @@ public class DLedgerCommitlogTest extends MessageStoreTestBase {
Assert.assertEquals(0, messageStore.dispatchBehindBytes());
doGetMessages(messageStore, topic, 0, 1700, 0);
messageStore.shutdown();
releaseMmapFilesOnWindows(dLedgerMmapFileStore.getDataFileList().getMappedFiles());
}
{
//Abnormal recover, left none commitlogs
@@ -281,6 +281,13 @@ public class DefaultMQAdminExt extends ClientConfig implements MQAdminExt {
return defaultMQAdminExtImpl.examineConsumerConnectionInfo(consumerGroup);
}
@Override
public ConsumerConnection examineConsumerConnectionInfo(
String consumerGroup, String brokerAddr) throws InterruptedException, MQBrokerException,
RemotingException, MQClientException {
return defaultMQAdminExtImpl.examineConsumerConnectionInfo(consumerGroup, brokerAddr);
}
@Override
public ProducerConnection examineProducerConnectionInfo(String producerGroup,
final String topic) throws RemotingException,
@@ -400,6 +400,21 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner {
return result;
}
@Override
public ConsumerConnection examineConsumerConnectionInfo(
String consumerGroup, String brokerAddr) throws InterruptedException, MQBrokerException,
RemotingException, MQClientException {
ConsumerConnection result =
this.mqClientInstance.getMQClientAPIImpl().getConsumerConnectionList(brokerAddr, consumerGroup, timeoutMillis);
if (result.getConnectionSet().isEmpty()) {
log.warn("the consumer group not online. brokerAddr={}, group={}", brokerAddr, consumerGroup);
throw new MQClientException(ResponseCode.CONSUMER_NOT_ONLINE, "Not found the consumer group connection");
}
return result;
}
@Override
public ProducerConnection examineProducerConnectionInfo(String producerGroup,
final String topic) throws RemotingException,
@@ -133,6 +133,10 @@ public interface MQAdminExt extends MQAdmin {
RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException, RemotingException,
MQClientException;
ConsumerConnection examineConsumerConnectionInfo(
String consumerGroup, String brokerAddr) throws InterruptedException, MQBrokerException,
RemotingException, MQClientException;
ProducerConnection examineProducerConnectionInfo(final String producerGroup,
final String topic) throws RemotingException,
MQClientException, InterruptedException, MQBrokerException;
@@ -48,6 +48,10 @@ public class ConsumerConnectionSubCommand implements SubCommand {
opt.setRequired(true);
options.addOption(opt);
opt = new Option("b", "brokerAddr", true, "broker address");
opt.setRequired(false);
options.addOption(opt);
return options;
}
@@ -62,7 +66,9 @@ public class ConsumerConnectionSubCommand implements SubCommand {
String group = commandLine.getOptionValue('g').trim();
ConsumerConnection cc = defaultMQAdminExt.examineConsumerConnectionInfo(group);
ConsumerConnection cc = commandLine.hasOption('b')
? defaultMQAdminExt.examineConsumerConnectionInfo(group, commandLine.getOptionValue('b').trim())
: defaultMQAdminExt.examineConsumerConnectionInfo(group);
System.out.printf("%-36s %-22s %-10s %s%n", "#ClientId", "#ClientAddr", "#Language", "#Version");
for (Connection conn : cc.getConnectionSet()) {
@@ -60,6 +60,10 @@ public class ConsumerStatusSubCommand implements SubCommand {
opt.setRequired(false);
options.addOption(opt);
opt = new Option("b", "brokerAddr", true, "broker address");
opt.setRequired(false);
options.addOption(opt);
opt = new Option("s", "jstack", false, "Run jstack command in the consumer progress");
opt.setRequired(false);
options.addOption(opt);
@@ -76,7 +80,9 @@ public class ConsumerStatusSubCommand implements SubCommand {
try {
defaultMQAdminExt.start();
String group = commandLine.getOptionValue('g').trim();
ConsumerConnection cc = defaultMQAdminExt.examineConsumerConnectionInfo(group);
ConsumerConnection cc = commandLine.hasOption('b')
? defaultMQAdminExt.examineConsumerConnectionInfo(group, commandLine.getOptionValue('b').trim())
: defaultMQAdminExt.examineConsumerConnectionInfo(group);
boolean jstack = commandLine.hasOption('s');
if (!commandLine.hasOption('i')) {
int i = 1;
@@ -311,6 +311,10 @@ public class DefaultMQAdminExtTest {
ConsumerConnection consumerConnection = defaultMQAdminExt.examineConsumerConnectionInfo("default-consumer-group");
assertThat(consumerConnection.getConsumeType()).isEqualTo(ConsumeType.CONSUME_PASSIVELY);
assertThat(consumerConnection.getMessageModel()).isEqualTo(MessageModel.CLUSTERING);
consumerConnection = defaultMQAdminExt.examineConsumerConnectionInfo("default-consumer-group", "127.0.0.1:10911");
assertThat(consumerConnection.getConsumeType()).isEqualTo(ConsumeType.CONSUME_PASSIVELY);
assertThat(consumerConnection.getMessageModel()).isEqualTo(MessageModel.CLUSTERING);
}
@Test