mirror of
https://github.com/apache/rocketmq.git
synced 2026-09-01 15:47:01 +08:00
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:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+27
@@ -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,
|
||||
|
||||
+1
-1
@@ -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_-]+$
|
||||
// %
|
||||
|
||||
@@ -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是否启动成功
|
||||
|
||||
@@ -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 的路径 |
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+7
-1
@@ -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()) {
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user